Java多线程:“基本篇”05之线程期待与叫醒
副标题#e#
wait(), notify(), notifyAll()等要领先容
在Object.java中,界说了wait(), notify()和notifyAll()等接口。wait()的浸染是让当前线程进入 期待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的浸染,则是唤 醒当前工具上的期待线程;notify()是叫醒单个线程,而notifyAll()是叫醒所有的线程。
Object类中关于期待/叫醒的API具体信息如下:
notify() — 叫醒在此工具监督器上期待的单个线程。
notifyAll() — 叫醒在此工具监督器上期待的所有线程。
wait() — 让当前线程处于“期待( 阻塞)状态”,“直到其他线程挪用此工具的 notify() 要领或 notifyAll() 要领”, 当前线程被叫醒(进入“停当状态”)。
wait(long timeout) — 让当前线程处于“期待(阻塞)状态”,“直到其他线程挪用此工具的 notify() 要领 或 notifyAll() 要领,可能高出指定的时间量”,当前线程被叫醒(进入“停当状态”) 。
wait(long timeout, int nanos) — 让当前线程处于“期待(阻塞)状态”, “直到其他线程挪用此工具的 notify() 要领或 notifyAll() 要领,可能其他某个线程间断当前线 程,可能已高出某个实际时间量”,当前线程被叫醒(进入“停当状态”)。
2. wait()和notify()示例
下面通过示例演示"wait()和notify()共同利用的景象"。
// WaitTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { synchronized (this) { System.out.println(Thread.currentThread().getName()+" call notify()"); // 叫醒当前的wait线程 notify(); } } } public class WaitTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { // 启动“线程t1” System.out.println(Thread.currentThread().getName()+" start t1"); t1.start(); // 主线程期待t1通过notify()叫醒。 System.out.println(Thread.currentThread().getName()+" wait()"); t1.wait(); System.out.println(Thread.currentThread().getName()+" continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行功效:
main start t1
main wait()
t1 call notify()
main continue
#p#副标题#e#
功效说明:
如下图,说明白“主线程”和“线程t1”的流程。
(01) 留意,图中"主线程" 代表“主线程main”。"线程t1" 代表 WaitTest中启动的“线程t1”。 而“锁” 代表“t1这个工具的同步锁 ”。
(02) “主线程”通过 new ThreadA("t1") 新建“线程t1”。随后 通过synchronized(t1)获取“t1工具的同步锁”。然后挪用t1.start()启动“线程 t1”。
(03) “主线程”执行t1.wait() 释放“t1工具的锁”而且进入“期待(阻 塞)状态”。期待t1工具上的线程通过notify() 或 notifyAll()将其叫醒。
(04) “线程t1”运行之后,通过synchronized(this)获取“当前工具的锁”; 接着挪用notify()叫醒“当前工具上的期待线程”,也就是叫醒“主线程”。
(05) “线程t1”运行完毕之后,释放“当前工具的锁”。紧接着,“主 线程”获取“t1工具的锁”,然后接着运行。
查察本栏目
对付上面的代码?曾经有个伴侣问到过:t1.wait()应该是让“线程t1”期待;可是,为什 么却是让“主线程main”期待了呢?
在解答该问题前,我们先看看jdk文档中关于wait的一段先容:
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object’s monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object’s monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
中文意思或许是:
#p#分页标题#e#
引起“当前线程”期待,直到别的一个线程挪用notify()或notifyAll()叫醒该线程。换句 话说,这个要领和wait(0)的结果一样!(增补,对付wait(long millis)要领,当millis为0时,暗示无限 期待,直到被notify()或notifyAll()叫醒)。
“当前线程”在挪用wait()时,必需拥有该工具的同步锁。该线程挪用wait()之后,会释 放该锁;然后一直期待直到“其它线程”挪用工具的同步锁的notify()或notifyAll()要领。 然后,该线程继承期待直到它从头获取“该工具的同步锁”,然后就可以接着运行。
留意:jdk的表明中,说wait()的浸染是让“当前线程”期待,而“当前线程” 是指正在cpu上运行的线程!
这也意味着,固然t1.wait()是通过“线程t1”挪用的wait()要领,可是挪用t1.wait()的 处所是在“主线程main”中。而主线程必需是“当前线程”,也就是运行状态,才 可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此, t1.wait()是让“主线程”期待,而不是“线程t1”!
3. wait(long timeout)和notify()
wait(long timeout)会让当前线程处于“期待(阻塞)状态”,“直到其他线程挪用此 工具的 notify() 要领或 notifyAll() 要领,可能高出指定的时间量”,当前线程被叫醒(进入 “停当状态”)。
下面的示例就是演示wait(long timeout)在超时环境下,线程被叫醒的环境。
// WaitTimeoutTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { System.out.println(Thread.currentThread().getName() + " run "); // 死轮回,不绝运行。 while(true) ; } } public class WaitTimeoutTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { // 启动“线程t1” System.out.println(Thread.currentThread().getName() + " start t1"); t1.start(); // 主线程期待t1通过notify()叫醒 或 notifyAll()叫醒,或高出3000ms延时;然后才 被叫醒。 System.out.println(Thread.currentThread().getName() + " call wait "); t1.wait(3000); System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行功效:
main start t1 main call wait t1 run // 约莫3秒之后...输出“main continue” main continue
功效说明:
如下图,说明白“主线程”和“线程t1”的流程。
(01) 留意,图中"主线程" 代表WaitTimeoutTest主线程(即,线程main)。"线程 t1" 代表WaitTest中启动的线程t1。 而“锁” 代表“t1这个工具的同步锁 ”。
(02) 主线程main执行t1.start()启动“线程t1”。
(03) 主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于 t1工具锁的线程通过notify() 可能 notifyAll()将其叫醒” 可能 “超时3000ms之后” ,主线程main才进入到“停当状态”,然后才可以运行。
(04) “线程t1”运行之后,进入了死轮回,一直不绝的运行。
(05) 超时3000ms之后,主线程main会进入到“停当状态”,然后接着进入“运行状 态”。
4. wait() 和 notifyAll()
通过前面的示例,我们知道 notify() 可以叫醒在此工具监督器上期待的单个线程。
下面,我们通过示例演示notifyAll()的用法;它的浸染是叫醒在此工具监督器上期待的所有线程。
public class NotifyAllTest { private static Object obj = new Object(); public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); ThreadA t2 = new ThreadA("t2"); ThreadA t3 = new ThreadA("t3"); t1.start(); t2.start(); t3.start(); try { System.out.println(Thread.currentThread().getName()+" sleep(3000)"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj) { // 主线程期待叫醒。 System.out.println(Thread.currentThread().getName()+" notifyAll()"); obj.notifyAll(); } } static class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public void run() { synchronized (obj) { try { // 打印输出功效 System.out.println(Thread.currentThread().getName() + " wait"); // 叫醒当前的wait线程 obj.wait(); // 打印输出功效 System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
运行功效:
t1 wait main sleep(3000) t3 wait t2 wait main notifyAll() t2 continue t3 continue t1 continue
功效说明:
参考下面的流程图。
(01) 主线程中新建而且启动了3个线程"t1", "t2"和"t3"。
#p#分页标题#e#
(02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的进程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会 执行obj.wait()期待其它线程通过notify()或额nofityAll()来叫醒它;沟通的原理,"t2"和 "t3"也会期待其它线程通过nofity()或nofityAll()来叫醒它们。
(03) 主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 叫醒obj上的期待线程,即叫醒 "t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj) 运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和 "t3"就可以获取“obj锁”而继承运行了!
5. 为什么notify(), wait()等函数界说在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“工具的同步锁”举办 操纵。
wait()会使“当前线程”期待,因为线程进入期待状态,所以线程应该释放它锁持有的 “同步锁”,不然其它线程获取不到该“同步锁”而无法运行!
OK,线程挪用wait()之后,会释放它锁持有的“同步锁”;并且,按照前面的先容,我们 知道:期待线程可以被notify()或notifyAll()叫醒。此刻,请思考一个问题:notify()是依据什么叫醒 期待线程的?可能说,wait()期待线程和notify()之间是通过什么关联起来的?谜底是:依据“对 象的同步锁”。
认真叫醒期待线程的谁人线程(我们称为“叫醒线程”),它只有在获取 “该工具的同步锁”(这里的同步锁必需和期待线程的同步锁是同一个), 而且挪用notify()或notifyAll()要领之后,才气叫醒期待线程。固然,期待线程被叫醒;可是,它不能 立即执行,因为叫醒线程还持有“该工具的同步锁”。必需比及叫醒线程释放了“工具 的同步锁”之后,期待线程才气获取到“工具的同步锁”进而继承运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是工具锁持有,并 且每个工具有且仅有一个!这就是为什么notify(), wait()等函数界说在Object类,而不是Thread类中的 原因。