[Java-多线程]“基础篇”05之 线程等待与唤醒

概要
本章,会对线程等待/唤醒方法进行介绍。涉及到的内容包括:

  1. wait(), notify(), notifyAll()等方法介绍
  2. wait()和notify()
  3. wait(long timeout)和notify()
  4. wait() 和 notifyAll()
  5. 为什么notify(), wait()等函数定义在Object中,而不是Thread中
    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3479224.html

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

结果说明:如下图,说明了“主线程”和“线程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.

中文意思大概是:
引起“当前线程”等待,直到另外一个线程调用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
t1main 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()的用法;它的作用是唤醒在此对象监视器上等待的所有线程。

 1 public class NotifyAllTest { 2  3 private static Object obj = new Object(); 4 public static void main(String[] args) { 5  6 ThreadA t1 = new ThreadA("t1"); 7 ThreadA t2 = new ThreadA("t2"); 8 ThreadA t3 = new ThreadA("t3"); 9  t1.start();10  t2.start();11  t3.start();12 13 try {14 System.out.println(Thread.currentThread().getName()+" sleep(3000)");15 Thread.sleep(3000);16 } catch (InterruptedException e) {17  e.printStackTrace();18  }19 20 synchronized(obj) {21 // 主线程等待唤醒。22 System.out.println(Thread.currentThread().getName()+" notifyAll()");23  obj.notifyAll();24  }25  }26 27 static class ThreadA extends Thread{28 29 public ThreadA(String name){30 super(name);31  }32 33 public void run() {34 synchronized (obj) {35 try {36 // 打印输出结果37 System.out.println(Thread.currentThread().getName() + " wait");38 39 // 唤醒当前的wait线程40  obj.wait();41 42 // 打印输出结果43 System.out.println(Thread.currentThread().getName() + " continue");44 } catch (InterruptedException e) {45  e.printStackTrace();46  }47  }48  }49  }50 }

运行结果
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue

结果说明:参考下面的流程图。
(01) 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
(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类中的原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,257评论 3 87
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,377评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,922评论 1 18
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 1,906评论 0 14
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,650评论 12 45