写在前面:
视频是什么东西,有看文档精彩吗?
视频是什么东西,有看文档速度快吗?
视频是什么东西,有看文档效率高吗?
诸小亮:下面我们聊聊——线程通信技术
张小飞:多个线程之间还可以通信吗?
诸小亮:是的,通过调用锁对象的wait()、notify()方法,可以实现线程通信
张小飞:那具体如何使用呢?
1. 体验
诸小亮:首先,Object 类中有 wait()、notify() 这两个方法
- wait():让当前线程进入等待状态
- 使用方式:锁对象.wait(),必须放到 同步代码块 或 同步方法 中
- notify():唤醒对应的正在 wait 的线程
- 使用方式:锁对象.notify(),必须放到 同步代码块 或 同步方法 中
- 调用 wait() 和 notify() 的锁对象必须是同一个
张小飞:大概明白,不过还需要您给出具体的代码
诸小亮:代码也给你准备好了,注意代码中的注释
class Hero implements Runnable{ //1. 搞一个锁对象 public static Object lock = new Object(); @Override public void run(){ synchronized (lock){ System.out.println(Thread.currentThread().getName() + "。。。。。。。获取锁,然后进入等待状态"); try { //2. 让当前线程等待,使用方式:锁对象.wait(),而且必须放到同步代码块或者同步方法中 lock.wait(); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "。。。。。。。结束"); } }}public class ThreadDemo { public static void main(String[] args) throws Exception { //3. 创建一个 yase 线程 Thread yase = new Thread(new Hero(),"yase"); yase.start(); //4. 主线程休眠2秒 Thread.sleep(2000); //5. 使用notify唤醒yase线程,让他继续执行 synchronized (Hero.lock){ System.out.println("主线程唤醒yase。。。。。。。"); //使用方式:锁对象.notify(),唤醒对应的正在等待状态的线程,必须放到同步代码块或者同步方法中 Hero.lock.notify(); } }}
结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/282eaeee730d43eb99940058ffa4b8d4.jpg)
诸小亮:你能解释一下这个代码的执行过程吗?
张小飞:当然可以
yase线程先执行,在执行 lock.wait() 代码后,进入等待状态main 线程在休眠 2 秒后,执行 Hero.lock.notify(); 去唤醒 yase 线程yase线程被唤醒后,接着执行,最后输出:yase。。。。。。。结束
诸小亮:不错啊
2. 注意点
诸小亮:使用 wait、notify时,需要特别注意一些地方
张小飞:都需要注意什么?
1. wait、notify必须放到同步代码块或同步方法中
诸小亮:第一,wait、notify必须放到同步代码块或同步方法中
张小飞:哦~,这个刚才说过,不过如果不放到它里面呢?
诸小亮:那调用 wait 方法时就会报错,比如:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/64be4adae33541bcbaa20e84b247edac.jpg)
结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/fbf6a6c14e0d4ab88343521f4b5a7421.jpg)
张小飞:明白了,不过这个异常是?
诸小亮:非法的监控状态异常,线程从启动到死亡有不同的状态,之后会解释
张小飞:好的
2. 锁对象是this
张小飞:如果锁对象是 this 呢?
诸小亮:如果锁对象是this,那么:this.wait(),记住必须是:锁对象.wait(),否则:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/026ad4781de2437a9cc54cb27ea35cba.jpg)
上图,锁对象是this,但是却调用 lock.wait();,结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/7bdeecbafbe94cd392377d55a7312c7e.jpg)
张小飞:记住了
3. 锁对象.notify()
张小飞:既然必须是 锁对象.wait(),那么也肯定必须是:锁对象.notify()
诸小亮:没错,否则也会报错,比如:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/349a9f5ee8464df9b5fdac3a5af4e811.jpg)
上图,锁对象是Hero.class,但是却调用Hero.lock.nodify();结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/300633c964924be1bf0ebad3476c270b.jpg)
4. 调用 wait() 和 notify() 的锁对象必须是同一个
诸小亮:还有,调用 wait() 和 notify() 的锁对象必须是同一个,否则:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/05030b9767ea4c4da859371cf45b20d9.jpg)
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/b88eedb01c0948d4934a4c1167e92d22.jpg)
结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/1555b5806b4c42d6a3a9222a39d7f195.jpg)
张小飞:看输出,程序一直没有结束吧
诸小亮:是的,其原因
- Hero.class.notify();只能唤醒锁对象是Hero.class 且 执行了 Hero.class.wait() 的线程
- yase线程的锁对象是lock,执行了lock.wait(),一直在等待被人唤醒,所以程序一直不结束
5. 执行 notify 后,正在 wait 的线程并不是立即运行
张小飞:我发现一个现象
诸小亮:什么现象?
张小飞:执行 notify 后,正在 wait 的线程并不是立即运行,需要等待执行 notify 的线程释放锁
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/5c3dedf6e9a543ca9150ecb7c5ac9d1a.jpg)
结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/c07507ae4ff04b9bbc333a9ade724ca5.jpg)
诸小亮:嗯,你说的很对,这也是一个需要关注的地方
6. wait()等待时,会释放锁对象
张小飞:还有一个问题,执行 wait() 方法后,是不是就释放锁对象了?
诸小亮:不错,只有释放了锁对象,main线程才能进入 synchronized 代码中
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/70e792aa3d3746e2b370c7e2ea94e75b.jpg)
上图可以看出,yase先执行,如果wait()不释放锁,那么就无法执行Hero.lock.notify()这句代码
7. 多个线程都在wait(),notify只会随机的唤醒一个
张小飞:如果有多个线程都调用了 wait() 方法呢?
诸小亮:问得好,不过,你何不自己试一试呢?
张小飞:嗯嗯,好的
public static void main(String[] args) throws Exception { //创建 2 个线程 Thread yase = new Thread(new Hero(),"yase"); yase.start(); Thread laoyase = new Thread(new Hero(),"laoyase"); laoyase.start(); Thread.sleep(2000); //使用方式:锁对象.notify(),唤醒对应的正在等待状态的线程,必须放到同步代码块或者同步方法中 synchronized (Hero.lock){ System.out.println("主线程唤醒。。。。。。。"); Hero.lock.notify();//有多个线程都在wait(),notify只会随机的唤醒一个 }}
结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/aaa0c58454c744849edfb7e2e039286b.jpg)
张小飞:结果出来了,yase线程结束了,但是 laoyase 这个线程还在阻塞
诸小亮:其实你多执行几次,会发现——多个线程都在wait(),notify只会随机的唤醒一个
张小飞:好的,我再试试
8. notifyAll()
张小飞:有没有可以把所有都在 wait 中的线程,都唤醒呢?
诸小亮:如果想全部唤醒,可以使用notifyAll(),或者执行多次notify(),比如:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/277156ad251f4ff8a1c0f34594a6bb57.jpg)
结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/f42788ed0b6a4ed3af0f2e22b2c8a3eb.jpg)
另外,执行多次notify()方法也可以,全部唤醒,比如:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/d09cc7cc64a34f5bbaab3c9664cab341.jpg)
结果:略
9. 特殊情况下的notify
诸小亮:特殊情况下的 notify 方法会唤醒所有
张小飞:这是什么意思?
诸小亮:来,看代码,注意代码中的注释
class Hero extends Thread{ public void run(){ //1. 注意这里的锁对象是this synchronized (this){ //2. 执行 notify 唤醒所有 this.notify(); System.out.println("lock线程运行----"+this.getName()); } }}public class ThreadDemo { public static void main(String[] args) throws Exception { //3. 创建一个Thread对象,作为锁对象 Hero yase = new Hero(); yase.setName("yase"); new Thread(new Runnable() { @Override public void run() { System.out.println("线程daji开始运行。。。。。。"); //4. 注意,这里的锁对象是一个线程对象 synchronized (yase){ try { yase.wait(); System.out.println("线程daji结束----"+yase.getName());//输出lock线程的名称 } catch (InterruptedException e) { } } } }, "daji").start(); new Thread(new Runnable() { @Override public void run() { System.out.println("线程lvbu开始运行。。。。。。"); //4. 注意,这里的锁对象是一个线程对象 synchronized (yase){ try { yase.wait(); System.out.println("线程vbul结束----"+yase.getName()); } catch (InterruptedException e) { } } } }, "lvbu").start(); Thread.sleep(2000); //5. 启动 yase 线程 yase.start(); }}
结果:
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/2eb065d082c34cccb385482f2c0fd64f.jpg)
张小飞:这个代码能否具体解释一下?
诸小亮:当然可以
首先创建一个名为 yase 的线程对象 然后创建了 daji、lvbu 两个线程,这两个线程都把 yase 对象作为锁对象 这两个线程分别启动后都执行 wait 方法,进入等待模式 最后yase线程启动,执行run方法时,调用 this.notify(); 结果:daji、lvbu 都被唤醒
张小飞:这是什么原理?
诸小亮:额。。。。,很抱歉我也没仔细研究过,暂时记住这个现象吧
张小飞:好的
3. 生产消费模式
诸小亮:接着我们说一个——生产消费模式
张小飞:这是做什么的?
诸小亮:生产者生产数据,消费者消费数据,先看一张图
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/2619ca3bd789471180d058e460a963e2.jpg)
张小飞:您是先说,面包师生产面包,放到面包柜,然后销售员再取走面包吗?
诸小亮:是的,这就是我们生活中的——生产消费模式
诸小亮:工作中,wait()、notify() 经常用于生产消费模式
张小飞:具体怎么使用呢?
诸小亮:给你一个需求,生产者生产商品,如果货架上已经有商品就不再生产,
消费者消费商品,如果货架上没有就通知生产者
示例:
class Goods{ String name = "哈根达斯"; //0:表示货架上没有商品,需要生产 //1:表示货架上已有商品,需要消费 int count = 0;}//消费者class Consumer implements Runnable{ private Goods goods; Consumer(Goods goods){ this.goods = goods; } @Override public void run(){ while (true){ synchronized (goods){ if(goods.count == 1){ System.out.println(Thread.currentThread().getName()+"。。。。。。消费商品---"+goods.count); goods.count = 0;//消费商品 goods.notify();//唤醒消费者 //让自己自己等待 try {goods.wait();} catch (InterruptedException e) {} } } } }}//生产者class Producer implements Runnable{ private Goods goods; Producer(Goods goods){ this.goods = goods; } @Override public void run(){ while (true){ synchronized (goods){ if(goods.count == 0){ System.out.println(Thread.currentThread().getName()+"。。。。。。生产商品---------"+goods.count); goods.count = 1;//生产商品,设置count=1 goods.notify();//唤醒消费者 //自己等待 try {goods.wait();} catch (InterruptedException e) {} } } } }}public class ThreadDemo { public static void main(String[] args) throws Exception { Goods goods = new Goods(); new Thread(new Producer(goods), "伊利").start(); new Thread(new Consumer(goods), "亚瑟").start(); }}
结果中,两个线程互相唤醒,交替执行
![张小飞的Java之路——第二十四章——多线程——wait_notify](https://p3.toutiaoimg.com/origin/tos-cn-i-qvj2lq49k0/ba10547d6aac4ab0a97ca47ea3ae1769.jpg)
诸小亮:怎么样,可以看懂吗?
张小飞:可以可以,原来这就是生产消费模式
4. wait 和 sleep
张小飞:我突然先到,sleep 也是可以暂停线程的
诸小亮:不错, wait 和 sleep 有相同点,也有区别,这也是面试常见的一个问题
张小飞:那么它们都有什么不同的地方呢?
诸小亮:都给你总结好了,自己看看吧
相同点:都可以暂时停止一个线程不同点: sleep必须指定睡眠时间,wait可以指定也可以不指定 sleep是 Thread 的静态方法,wait是 Object 类的成员方法 sleep 不释放锁,wait 释放锁 sleep 等待一定时间后自定运行,wait需要 notify 或 notifyAll 唤醒 wait 必须配合synchronized使用,sleep不必 sleep 会让线程进入 TIMED_WAITING 状态,wait让线程进入 WAITING 状态(之后会详细解释线程状态)