java基础第13天
来源:互联网 发布:网络发展趋势 编辑:程序博客网 时间:2024/04/28 20:48
1:多线程
(1)多线程
进程: 当前正在运行的程序!
线程:进程在执行过程中,可能需要多个任务同时执行,每个任务的执行者就是线程。线程就是进程中的一个执行控制单元。
单线程:
多线程:如果一个进程中有多个执行的轨迹,那么就是同时有多个线程在执行代码。
这样的程序叫多线程程序!
线程是部分,进程是整体
线程是部分,而进程整体。
部分不能单独存在,线程不能单独启动,只有进程启动后线程才可以启动。
一个进程中的多个线程,可以共享这个进程的数据。进程之间不能共享数据。
多线程的用途
同时执行多个任务,可以提高效率。
可以完成多个任务并发的效果。
(2)jvm启动是多线程的。
(3)如果实现多线程呢?
A:继承Thread类
步骤:
**定义一个类,继承Thread类
**重写run方法
**调用start方法.
**启动线程
**调用run方法
Thread类中的常用方法:
**Thread(String name):通过构造给线程起名字
**setName(String name):通过set方法给线程起名字
**getName():获取线程的名称
**currentThread():获取当前线程的一个对象引用
***获取哪些没有直接继续Thread类的线程名称
B:实现Runnable接口
步骤:
**定义一个类,实现Runnable接口
**重写run方法
**创建实现了Runnable接口的子类对象,并把该对象作为参数传递给
Thread类的对象.这个时候,调用Thread类的start方法.
(4)
多线程的真实情况(CPU很忙)
1 多个进程同时运行是假的!多个线程同时运行也是假的!
理解:多个线程就好比需要通电才能运行的多个机器人!但我们只能一个电源,这个电源就是CUP。
2 线程的随机性,以及时间分片
线程的运行时间分派由 CPU来决定,如果多有多个线程,那么CPU也不会有顺序的去执行它们,而是随机的。
当一个线程运行一段时间之后,应该主动阻塞,这样CPU就会把资源分派给其他线程。如果线程不合作,自己不主动阻塞,那么CPU会在一段时间之后强行停止这个线程的运行,把资源分派给其他线程。
CPU会为每个线程都指定一个时间分片,当时间片用完时,线程还不阻塞,那么就强行停止它。
3 多核CPU的不同
多核CPU就相当于是多个CPU,那么与上面说的效果就不太一样了。
4 同一个多线程程序多次运行,结果可能不一样
因为线程的随机性。
5 主线程结束时,JVM是否结束
No,只有所有线程都结束了,JVM才会线程。
6 多线程程序的调用栈
每个线程都有自己的调用栈,都有自己的执行轨迹!
7 当某个线程出现异常时
异常不能跨线程。
如果是main抛出了异常,那么主线程会终止,但其他线程不会终止。
如果t1线程抛出了异常,那么t1终止,而main不会终止。
Thread类一般性方法
1 设置线程的名字
Thread(String name):这个构造器可以用来设置线程名称
Thread#setName(String name):这个方法可以用来设置线程名称
Thread#getName():获取当前线程名称
2 获取主线程对象
static Thread Thread.currentThread():这个方法用来获取当前线程。
该线程,当前线程。
Runnable接口
1 当一个类需要继承另一个类后,就不能再去继承Thread类了
一个类只能有一个父类,这时如果我们继承了Thread,那么就不能再去继承别的类了。那么我们就不能实现多线程程序了。
使用Runnable接口。
Runnable接口中只有一个方法run(),其实Thread类也是实现了Runnable接口才有的run()方法。
2 Thread是完成任务的人,而Runnable就是任务
Runnable是任务,而Thread是完成任务的人,这样我们的程序也会清晰一些!
3 实现Runnable
Runnable r = new Runnable() {public void run() {…}};
4 在Runnable的run()里获取当前线程
Thread th = Thread.currentThread();
练习
1 售票
实现多窗口同时售票!
多个窗口共享同一票库,当票库无票时,程序结束。
分析:
票库类:
票数属性
构造器:指定票数;
查看当前票数方法;
出票方法:返回当前票码,把票数减1。
窗口类(多个窗口需要并发出票,所以需要是线程类):
构造器:指定票库
run(死循环出票):
判断当前票数是否小于等于0,如果是就跳出循环;
调用票库出票方法,打印当前票码!
测试类:
创建票库对象;
创建窗口对象;
启动窗口线程。
卖票的问题
A:继承Thread实现卖票
这个时候,我们的票需要定义为静态的.但是呢,我们不建议这样做.
B:实现Runnable接口卖票
在这里,我们就发现卖票除了问题.(线程安全问题)
线程安全问题怎么产生的?
**多个线程延迟访问
**线程的随机性
重点:
多线程的两种实现.
线程的生命周期.理解
用同步实现卖票.
线程的状态
1 线程的生命周期包含状态
新状态、就绪状态、阻塞状态、死亡状态
2 新状态
刚刚new出来的线程对象,还没有调用start()方法。如:Thread t = new Thread();
3 就绪状态(可运行状态)
t.start()之后中,线程进入就绪状态!
在某一个时间点上,只有一个线程是运行着的,其它的线程都没有运行,所以我们说 start()之后不是“运行状态”,而是“就绪状态”。
线程什么时候从就绪运行,这由CPU来决定, CPU会给运行的线程一个时间片,这 个时间用完,如果线程还没有主动阻塞,那么CPU会强行停止运行,给其他线程运行机会。
4 运行状态
由 CPU决定, CPU会在就绪状态的线程中随机选择一个,给其运行的时间片。运行的 线程,应该主动进入阻塞状态,这样给其他线程运行的时间。
5 阻塞状态
休眠:Thread.sleep(1000),当线程执行了sleep()方法,那么这个线程就进入了休眠 状态。休眠的线程必须要等到指定的毫秒过完,才能返回到就绪状态。
等待:当前线程执行了wait()方法,进入了对象的等待列表中。只能期 待其他线程调用notify()或notifyAll()来唤醒这个等待的线程。
挂起:调用了该线程的suspend()方法,那么这个线程就挂起了。这时就期待该线程 的resume()被调用,才能回到就绪状态。但是,这两个方法都被作废了,你不应该使用它们!
IO阻塞:当线程正在完成IO操作,那么这个线程也就阻塞了。直到IO操作完成 了!
锁定: 当线程要使用的对象被其他线程使用时,那么这个线程进入了锁定状态。 直到线程得到了要使用的对象后,那么就回到就绪状态。
6 死亡状态
run()方法结束,正常死亡! (正常死)
run()中抛出了异常,因为而死亡! (异常死)
run()被杀了,有人调用这个线程的stop()方法。stop()方法被作废了!被打死(作废了)
同步互斥
同步的目的:用来处理多个线程共享同一数据时,所造成的错误。
1 多线程相互影响,出现错误数据
两个人同时过一扇门
两个人同时用一个电话
2 synchronized块的语法格式
synchronized(监视器对象) {
……
}
共享数据不可能是局部变量,因为局部变量,都是每个线程都有一份自己的拷贝!共享数据是属性。
3 synchronized块的作用
理解:
把监视器对象理解为导游;
把同步块中的代码理解为景区内容;
把线程理解为游客!
当游客想进入景区游玩时,需要有导游陪同,如果a游客来到景区时,导游空闲,那么导游会陪同a游客进入景区,在a游客没有离开景区之前,b游客也要进入景区,那么这时导游不在,所以b游客需要等待导游空闲。当a游客离开了景区,那么导游就空闲了,这时b游客就可以由导游陪同进入景区了。
a和b两个线程在同步块内容上同步互斥了,当a进入同步块后,b需要等待a退出后才能进入同步块。
synchronized方法
1 使用synchronized关键字来修饰方法
public synchronized void fun() {
}
调用同步方法同样需要钥匙,钥匙在保安手里。保安就是当前对象!
2 注意,synchronized方法与synchronized块是相同的道理
同步块的共享数据(监视器对象)是在圆括号里
同步方法的共享数据(监视器对象)是 this,即当前对象。
同步方法:当a线程访问了o对象的fun1()方法时,同时b线程也要访问o对象的fun2()方法,这时就需要等待a线程退出o对象的fun1()方法,即不在使用o对象时,那么b线程才能进入o对象的fun2()方法。
但非同步方法,其他线程还是可以访问的!
3 使用同步块还是同步方法
同步块!
灵活啊~
静态同步方法
1 静态同步方法是可以存在的
静态同步方法是可以存在的,同一个类中的多个静态同步方法之间可以同步互斥!
2 静态同步方法的监视器对象
共享数据是静态同步方法所在类的class对象,例如:
public class A {
public static synchronized void fun() {}
}
对于fun()方法来说,共享数据是A.class
懒汉式
1 使用synchronized关键字来修饰方法
public synchronized void fun() {
}
调用同步方法同样需要钥匙,钥匙在保安手里。保安就是当前对象!
2 注意,synchronized方法与synchronized块是相同的道理
同步块的共享数据(监视器对象)是在圆括号里
同步方法的共享数据(监视器对象)是 this,即当前对象。
同步方法:当a线程访问了o对象的fun1()方法时,同时b线程也要访问o对象的fun2()方法,这时就需要等待a线程退出o对象的fun1()方法,即不在使用o对象时,那么b线程才能进入o对象的fun2()方法。
但非同步方法,其他线程还是可以访问的!
3 使用同步块还是同步方法
同步块!
灵活啊~
静态同步方法
1 静态同步方法是可以存在的
静态同步方法是可以存在的,同一个类中的多个静态同步方法之间可以同步互斥!
2 静态同步方法的监视器对象
共享数据是静态同步方法所在类的class对象,例如:
public class A {
public static synchronized void fun() {}
}
对于fun()方法来说,共享数据是A.class
懒汉式
1 使用synchronized关键字来修饰方法
public synchronized void fun() {
}
调用同步方法同样需要钥匙,钥匙在保安手里。保安就是当前对象!
2 注意,synchronized方法与synchronized块是相同的道理
同步块的共享数据(监视器对象)是在圆括号里
同步方法的共享数据(监视器对象)是 this,即当前对象。
同步方法:当a线程访问了o对象的fun1()方法时,同时b线程也要访问o对象的fun2()方法,这时就需要等待a线程退出o对象的fun1()方法,即不在使用o对象时,那么b线程才能进入o对象的fun2()方法。
但非同步方法,其他线程还是可以访问的!
3 使用同步块还是同步方法
同步块!
灵活啊~
静态同步方法
1 静态同步方法是可以存在的
静态同步方法是可以存在的,同一个类中的多个静态同步方法之间可以同步互斥!
2 静态同步方法的监视器对象
共享数据是静态同步方法所在类的class对象,例如:
public class A {
public static synchronized void fun() {}
}
对于fun()方法来说,共享数据是A.class
懒汉式
死锁
1 什么叫死锁
张三在A电话亭想去B电话亭
李四在B电话亭想去A电话亭
2 理解死锁
a线程锁定一个资源,同时想获取b线程的资源
b线程锁定一个资源,同时想获取a线程的资源。
2 通常死锁是怎么造成的
同步嵌套调用造成的。
3 死锁的代码
数据类
A类两个同步方法fun1()和fun2();
fun1()需要一个本类型的参数,例如void fun1(A a);
fun1()中使用参数调用fun2()方法,例如:a.fun2();
fun2()的内容无关紧要,甚至可以没有内容。
线程类:
创建一个线程类,需要两个A类的对象:from和to;
在run()中使用from调用fun1()方法,参数为to;
测试类:
创建两个数据对象:zhangSan和liSi;
创建两个线程对象:一个from是zhangSan,to是liSi;另一个from是liSi,to是zhangSan;
启动两个线程。
为了看到效果,可以在fun1()方法中调用fun2()之前,添加Thread.sleep()方法。
解决线程安全问题
A:同步代码块
格式: synchronized(对象)
{
//被同步的代码,也就是可能出现安全问题的代码
}
B:同步方法
就是在方法上加synchronized关键字即可.
C:同步的前提:
**同步需要两个或者两个以上的线程
**多个线程使用的是同一个锁。
未满足这两个条件,不能称其为同步。
D:同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,
无形中会降低程序的运行效率。
同步可能造成死锁问题:
死锁的产生:
你拿着我们的锁,我拿着你的锁,谁都不先对方用.
举例:吃饭,西餐(刀和叉)
A:有两个刀
B:有两个叉
针对程序来说,就是我想用你的锁,而你也想用我的锁.都不放手.导致了死锁.
用代码实现一下死锁.
解决死锁的方式:不写这样的代码就可以.
线程间的通讯
wait()、notify()、notifyAll()
通讯方法,这三个方法都是Object类的方法。
通讯方法被调用的前提是:同步环境!
1 什么叫线程间的通讯
多个线程之间需要协调工作。
例如:
1 女:我们结婚吧;
2 男:我还没有钱;你等我,我去赚钱!
3 女:ok,那我wait了,你赚了钱回来,不要忘了notify一下我。
1 老师:我们开始上课;
2 学生A:老师等一下,我要去厕所;
3 老师:OK,你快点,那我wait()了,等你回来notify我一下。
1 坏人:你爷爷在我手里
2 好人:不要伤害我的爷爷
3 坏人:给我钱,我去公安局边上的垃圾筒中去取,我先wait了,你把钱放好了,再notify()我
4 好人:好吧,我去借钱!
2 实现绑架1(需要使用相同的垃圾筒)
好人类(Runnable) -- 把钱放到垃圾筒
坏人类(Runnable) -- 从垃圾筒取钱
垃圾筒(钱的数量,存钱、取钱、当前钱数)
因为好人与坏人使用的是不同的垃圾筒,所以会导致好人存钱了,但坏人也取不到钱。
3 实现绑架2(坏人可能先取钱)
修改上例,让好人、坏人共用同一个垃圾筒
因为坏人可能会在好人存钱之前去取钱,所以会取不到!
4 实现绑架3(同步中使用不同的监视器)
修改上例,让垃圾筒同步。确保在好人已经使用垃圾筒时,坏人需要等待好人使用之后才能使用。
因为使用了不同的监视器,没有实现同步效果。好人和坏人共用的是垃圾筒,所以应该使用垃圾筒来做监视器。
如果坏人先使用了垃圾筒,而好人需要等待坏人使用完才能进入同步块,那么就失败了。
5 实现绑架4(当坏人发现没钱时,等待好人存钱)
如果坏人先得到垃圾筒,发现没有钱,可以等待好人来存钱,而坏人停止运行。
好人存钱之后会唤醒坏人。
坏人再去取钱。
通讯原理
1 使用wait()、notify()、notifyAll()完成通讯
这三个方法我们称之为通讯地方法
2 同步与线程间通讯方法
没有同步环境,就不能完成线程间的通讯!
如果你的通讯方法没有在同步块或同步方法中,那么会抛出异常!
wait():声明了InterruptedException异常!在使用wait()时,还要使用try/catch。
3 监视器对象与线程通讯方法
只能使用监视器对象来调用线程通讯方法。
4 监视器对象的监狱
每个监视器对象都有一个线程监狱,当执行了wait()时,例如:a.wait(),那么就是把当前线程关到a的监狱中。
当其他线程执行a.nofity()时,那么就是把a监狱中任意一个线程释放出来。
当其他线程执行a.notifyAll时,那么就会把a监狱中所有线程都放出来。
5 等待状态的线程会释放对象锁
当一个线程进入了等待状态后,那么它会释放对象锁,让其他线程可以进入同步环境。
6 被唤醒的线程会在醒来的位置向下运行
当一个线程,从wait()后,被唤醒,并不代表马上就能执行。这需要等到重新获取到对象锁!
6 线程间通讯小结
使用wait()、notify()、notifyAll()方法可以完成线程间的通讯,可叫它们通讯方法;
只能在同步环境下调用通讯方法;
只能使用监视器对象调用通讯方法;
每个监视器对象都有一个线程监狱:执行a.wait()的线程会被关押到a对象的线程监狱中;
若想释放出a对象的线程监狱中的线程,那么需要调用a.notify()文法,该方法只能保证在a对象的线程监狱中释放出一个线程,但不能保证释放的是哪一个;
还可以使用a.notifyAll()方法释放出a对象的监狱中关押的所有线程。
被wait()了的线程不能自己恢复到就绪状态,只能等待其他线程调用同一监视器对象上的notify()或notifyAll()方法来唤醒。
被wait()了的线程会释放监视器对象的对象锁,这样其他线程就可以进入他占用的同步环境。
被唤醒的线程恢复到了就绪状态,当再次获取监听器对象的锁后会在wait()处向下运行。
(1)线程间通讯就是:想把数据你一个,我一个的输出. 重点
wait:让线程等待
notify:唤醒线程池中的第一个线程
notifyAll:唤醒线程池中的所有线程
(2)wait(),sleep()有什么区别?面试问题
A:wait()可有无参数的调用.
而sleep必须指定睡眠时间.但是有些时候,睡眠时间不好确定.这个时候就可以使用wait.
B:wait:释放了执行权,释放锁.
sleep:释放了执行权,不释放锁.
sleep有醒过来的时候,而wait可能醒不了
(3)停止线程的方式?
A:通过stop方法停止线程.但是这个方法过时,所以不推荐使用.使用 interrupt方法来中断该等待
B:线程中的代码一般都是在循环中的.可以通过控制循环的次数来让run方法结束.
后台线程(守护线程)
JVM会在所有线程结束后结束!
当所有的非后台线程结束后,所有后台线程集体自杀!
1 什么是后台线程
JVM会在所有线程结束后结束!
当所有的非后台线程结束后,所有后台线程集体自杀!
2 什么时候用后台线程
当你的线程只是为其他线程服务的,那么当其他线程结束后,这个服务线程就没有独立存在的意义了,那么就把这个线程设置为后台线程。
例如:垃圾回收器,在我们的main方法结束后(所有我们写的线程),垃圾回收器没有道理自己还运行,那么可以理解为垃圾回收器是个后台线程。
3 如何设置线程为后台线程
Thread th = …
th.setDaemon(true);//后台了,注意,必须在start()之前进行设置。
th.start();
Thread.currentThread().setDaemon(true);//无意义!
4 线程运行后是否还可以设置为后台线程
不能!!!必须在start()之前完成设置
5 后台线程的子线程
后台线程的子线程,也是默认为后台线程!
什么叫子线程?A线程的任务中启动了B线程,那么B是A的子线程。
所有的后台线程子线程默认为后台线程!
合并线程(join)
1 什么合并线程
join()是Thread类的方法。
Thread th = new …
th.start();
…
th.join();//使当前线程等待th线程结束后,再向下运行!
2 join()方法的作用
使当前线程等待该线程结束再向下运行。
Thread#join()声明了一个InterruptedException,我们已经知道有三个方法声明了这个异常。
Thread.sleep():Thread类的static方法sleep()
Thread#join:Thread类的实例方法join
Object#wait():Object类的实例方法wait()
线程让步(yield)
在以前使用sleep()的地方,你感觉sleep会担负时间,那么可以尝试使用yield
1 yield()方法的作用
说线程已经把最重要的工作做完了,告诉CPU可以切换给其他线程了。
2 多核CPU下yield()方法的效果
不明显!
3 让步不同与阻塞
让步的线程没有进入阻塞状态,只是从运行状态到就绪状态!
终止线程(比join和yield重要一点)
线程对象都有一个中断值,它是一个boolean类型的属性。这个中断值默认是false!你可以调用一些方法来修改中断值,也可以调用一些访问获取中断值。
void interrupt():把中断值修改为true;
boolean isInterrupted():获取中断值;
static boolean interrupted():获取中断值,并把中断值修改为false。
1 stop()方法
th.stop();//把th杀死!
但stop()作废了!你不应该再去使用它!
2 使用interrupt()方法,isInterrupted()方法和interrupted()的作用
3 中断异常相关方法
InterruptedException
sleep()
join()
wait()
这三个方法都会在中断值为true时,抛出异常!并且会把中断值修改为false。
优先级(鸡肋)
1 什么是线程的优先级
CPU会优先运行优先级高的线程!
2 Java中线程的优先级 范围(1-10); 默认是5.
3 Java中线程的优先级依赖OS
不同的OS,支持的优先级别不同,windows是7个,Linux一些版本还有1个的。
所以优先级不可信,如果你真想让程序有个选择执行的话,使用sleep()好一点。
4 优先级不可靠,不能依赖优先级
- java基础第13天
- java基础第1天
- java基础第2天
- java基础第3天
- java基础第4天
- java基础第5天
- java基础第6天
- java基础第7天
- java基础第8天
- java基础第9天
- java基础第10天
- java基础第11天
- java基础第12天
- java基础第14天
- java基础第15天
- java基础第16天
- java基础第17天
- java基础第18天
- BAE: 一个可以尝试的Python云平台
- 新手建站初期,选择虚拟主机的方法
- C++中使用接口
- 实验一 Linux基本环境
- Android源码目录结构详解
- java基础第13天
- Android里Service的bindService()和startService()混合使用深入分析
- java基础第14天
- 《Effective Java》读书笔记04--避免创建不必要的对象
- 大数据
- 嵌入式Linux笔记
- 如何诊断oralce 运行突然变慢
- 2013.3.23-5简介struct cmsghdr结构
- vim基本命令