在开始学习wait、notify和notifyAll 之前,先稍微讲讲wait set。所有实例都有一个wait set,wait set是一个在执行该实例的wait方法时、操作停止的线程的集合它有点类似线程的休息室,而且每个实例都会有。
执行wait方法时,线程便会暂时停止操作,进入 wait set这个休息室。除非发生下列其中一种情况,否则线程会永远被留在这个wait set 里。
- 有其他线程以notify 方法唤醒该线程;
- 有其他线程以notifyA方法唤醒该线程;
- 有其他线程以interrupt方法唤醒该线程;
- wait 方法已经到期。
Wait方法——把线程放入wait set:
obj.wait();
则目前的线程停止执行,进入实例obj的的wait set.这个操作成为:
线程在obj上wait().(这个obj不是线程而是对象)
wait(); this.wait();意义相同,执行wait的线程就会进入this的wait set.此时就变成了在this上wait.
如欲执行wait()方法,线程需获取锁定(这是规则)。
但是当线程进入wait set时,已经释放了该实例的锁定。
Notify方法——从wait set拿出线程:
obj.notify();(这个obj也是对象,不是线程)则从wait set里的线程中挑出一个,唤醒这个线程。被唤醒的线程便退wait set
Notify后的线程:
被notify唤醒的线程不是在notify后立即执行,因为在notify的那一刻,执行notify 的线程还握着锁定不放,所以其他线程无法获取该实例的锁定。需要重新争夺锁。
Notify如何选择线程:
假设执行notify方法时,wait set里面正在执行的线程不止一个。规格并没有注明此时该选择哪一个线程。究竟是选择等待线程里面的第一个,随机选择或是另以其他方式选择,则以java处理系统而异。
notifyAll()方法——从wait set 拿出所有线程:
跟wait方法和notify方法一样,线程必须要获取要调用实例的锁定,才能调用notifyAll方法。
若没有锁定的线程去调用wait,notify或notifyAll时,便会抛出异常java.lang.IllegalMonitorStateException.
Notify方法和notifyAll方法两者非常相似,到底该用哪一个?老实说,这个选择有点难。选择notify的话,因为要唤醒的线程比较少,程序处理速度当然要比notifyAll略胜一筹。但是选择notify时,若这部分程序处理的不好,可能会有程序挂掉的危险性,一般说来,选择notifyAll所写出来的程序代码要比选择notify可靠。除非你能确定程序员对程序代码的意义和能力限度一清二楚,否则选择notifyAll应该比较稳扎稳打
注意:
wait,notify,notifyAll是Object类的方法
obj.wait()是把现在的线程放到obj的wait set
obj.notify()是从obj的wait set里唤醒一个线程
obj.notifyAll()是唤醒所有在obj的wait set里的线程
换句话说,把wait、notify、notifyAll三者均解释为对实例对象的wait set的操作,会比说他们是对线程的操作更贴切,由于所有实例都会有wait set,所以wait、notify、notifyAll才会是Object类的方法。
虽然三者不是Thread类固有的方法,不过,因为Object类是Java所有类的祖先类,所以wait、notify、notifyAll也是Thread的方法。
线程的状态:
1. 新建( new ):新创建了一个线程对象。
2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权 。
3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:
Blocked(堵塞)状态:
- 同步锁;
- 调用了sleep()和join()方法进入Sleeping状态;
- 执行wait()方法进入Waiting状态,等待其他线程notify通知唤醒);
- (一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。
- (二). 同步阻塞:运行( running )的线程在获取对象的同步锁时(synchronized),若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
- (三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
在java中官方是这么定义的: