写在前面:
视频是什么东西,有看文档精彩吗?
视频是什么东西,有看文档速度快吗?
视频是什么东西,有看文档效率高吗?
诸小亮:下面我们聊聊——线程通信技术
张小飞:多个线程之间还可以通信吗?
诸小亮:是的,通过调用锁对象的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();
}
}
}
结果:
诸小亮:你能解释一下这个代码的执行过程吗?
张小飞:当然可以
yase线程先执行,在执行 lock.wait() 代码后,进入等待状态
main 线程在休眠 2 秒后,执行 Hero.lock.notify(); 去唤醒 yase 线程
yase线程被唤醒后,接着执行,最后输出:yase。。。。。。。结束
诸小亮:不错啊
2. 注意点
诸小亮:使用 wait、notify时,需要特别注意一些地方
张小飞:都需要注意什么?
1. wait、notify必须放到同步代码块或同步方法中
诸小亮:第一,wait、notify必须放到同步代码块或同步方法中
张小飞:哦~,这个刚才说过,不过如果不放到它里面呢?
诸小亮:那调用 wait 方法时就会报错,比如:
结果:
张小飞:明白了,不过这个异常是?
诸小亮:非法的监控状态异常,线程从启动到死亡有不同的状态,之后会解释
张小飞:好的
2. 锁对象是this
张小飞:如果锁对象是 this 呢?
诸小亮:如果锁对象是this,那么:this.wait(),记住必须是:锁对象.wait(),否则:
上图,锁对象是this,但是却调用 lock.wait();,结果:
张小飞:记住了
3. 锁对象.notify()
张小飞:既然必须是 锁对象.wait(),那么也肯定必须是:锁对象.notify()
诸小亮:没错,否则也会报错,比如:
上图,锁对象是Hero.class,但是却调用Hero.lock.nodify();结果:
4. 调用 wait() 和 notify() 的锁对象必须是同一个
诸小亮:还有,调用 wait() 和 notify() 的锁对象必须是同一个,否则:
结果:
张小飞:看输出,程序一直没有结束吧
诸小亮:是的,其原因
- Hero.class.notify();只能唤醒锁对象是Hero.class 且 执行了 Hero.class.wait() 的线程
- yase线程的锁对象是lock,执行了lock.wait(),一直在等待被人唤醒,所以程序一直不结束
5. 执行 notify 后,正在 wait 的线程并不是立即运行
张小飞:我发现一个现象
诸小亮:什么现象?
张小飞:执行 notify 后,正在 wait 的线程并不是立即运行,需要等待执行 notify 的线程释放锁
结果:
诸小亮:嗯,你说的很对,这也是一个需要关注的地方
6. wait()等待时,会释放锁对象
张小飞:还有一个问题,执行 wait() 方法后,是不是就释放锁对象了?
诸小亮:不错,只有释放了锁对象,main线程才能进入 synchronized 代码中
上图可以看出,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只会随机的唤醒一个
}
}
结果:
张小飞:结果出来了,yase线程结束了,但是 laoyase 这个线程还在阻塞
诸小亮:其实你多执行几次,会发现——多个线程都在wait(),notify只会随机的唤醒一个
张小飞:好的,我再试试
8. notifyAll()
张小飞:有没有可以把所有都在 wait 中的线程,都唤醒呢?
诸小亮:如果想全部唤醒,可以使用notifyAll(),或者执行多次notify(),比如:
结果:
另外,执行多次notify()方法也可以,全部唤醒,比如:
结果:略
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();
}
}
结果:
张小飞:这个代码能否具体解释一下?
诸小亮:当然可以
首先创建一个名为 yase 的线程对象
然后创建了 daji、lvbu 两个线程,这两个线程都把 yase 对象作为锁对象
这两个线程分别启动后都执行 wait 方法,进入等待模式
最后yase线程启动,执行run方法时,调用 this.notify();
结果:daji、lvbu 都被唤醒
张小飞:这是什么原理?
诸小亮:额。。。。,很抱歉我也没仔细研究过,暂时记住这个现象吧
张小飞:好的
3. 生产消费模式
诸小亮:接着我们说一个——生产消费模式
张小飞:这是做什么的?
诸小亮:生产者生产数据,消费者消费数据,先看一张图
张小飞:您是先说,面包师生产面包,放到面包柜,然后销售员再取走面包吗?
诸小亮:是的,这就是我们生活中的——生产消费模式
诸小亮:工作中,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();
}
}
结果中,两个线程互相唤醒,交替执行
诸小亮:怎么样,可以看懂吗?
张小飞:可以可以,原来这就是生产消费模式
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 状态(之后会详细解释线程状态)