张小飞的Java之路——第二十四章——多线程——wait_notify

本文详细介绍了Java中线程通信的关键技术wait和notify,通过实例展示了它们的使用方法和注意事项,包括wait()、notify()必须在同步环境中使用,锁对象的一致性,以及notify()唤醒线程的机制等。此外,还探讨了生产消费模式和wait与sleep的区别。
摘要由CSDN通过智能技术生成

写在前面:

视频是什么东西,有看文档精彩吗?

视频是什么东西,有看文档速度快吗?

视频是什么东西,有看文档效率高吗?


诸小亮:下面我们聊聊——线程通信技术

张小飞:多个线程之间还可以通信吗?

诸小亮:是的,通过调用锁对象的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();
        }
    }
}

结果:

image.png

诸小亮:你能解释一下这个代码的执行过程吗?

张小飞:当然可以

yase线程先执行,在执行 lock.wait() 代码后,进入等待状态

main 线程在休眠 2 秒后,执行 Hero.lock.notify(); 去唤醒 yase 线程

yase线程被唤醒后,接着执行,最后输出:yase。。。。。。。结束

诸小亮:不错啊

2. 注意点

诸小亮:使用 wait、notify时,需要特别注意一些地方

张小飞:都需要注意什么?

1. wait、notify必须放到同步代码块或同步方法中

诸小亮:第一,wait、notify必须放到同步代码块或同步方法中

张小飞:哦~,这个刚才说过,不过如果不放到它里面呢?

诸小亮:那调用 wait 方法时就会报错,比如:

image.png

结果:

image.png

张小飞:明白了,不过这个异常是?

诸小亮:非法的监控状态异常,线程从启动到死亡有不同的状态,之后会解释

张小飞:好的

2. 锁对象是this

张小飞:如果锁对象是 this 呢?

诸小亮:如果锁对象是this,那么:this.wait(),记住必须是:锁对象.wait(),否则:
image.png

上图,锁对象是this,但是却调用 lock.wait();,结果:

image.png

张小飞:记住了

3. 锁对象.notify()

张小飞:既然必须是 锁对象.wait(),那么也肯定必须是:锁对象.notify()

诸小亮:没错,否则也会报错,比如:

image.png

上图,锁对象是Hero.class,但是却调用Hero.lock.nodify();结果:

image.png

4. 调用 wait() 和 notify() 的锁对象必须是同一个

诸小亮:还有,调用 wait() 和 notify() 的锁对象必须是同一个,否则:

image.png

image.png

结果:

image.png
张小飞:看输出,程序一直没有结束吧

诸小亮:是的,其原因

  • **Hero.class.notify();只能唤醒锁对象是Hero.class 且 执行了 Hero.class.wait() 的线程 **
  • yase线程的锁对象是lock,执行了lock.wait(),一直在等待被人唤醒,所以程序一直不结束

5. 执行 notify 后,正在 wait 的线程并不是立即运行

张小飞:我发现一个现象

诸小亮:什么现象?

张小飞:执行 notify 后,正在 wait 的线程并不是立即运行,需要等待执行 notify 的线程释放锁
image.png

结果:

image.png

诸小亮:嗯,你说的很对,这也是一个需要关注的地方

6. wait()等待时,会释放锁对象

张小飞:还有一个问题,执行 wait() 方法后,是不是就释放锁对象了?

诸小亮:不错,只有释放了锁对象,main线程才能进入 synchronized 代码中

image.png

上图可以看出,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只会随机的唤醒一个
    }
}

结果:

image.png

张小飞:结果出来了,yase线程结束了,但是 laoyase 这个线程还在阻塞

诸小亮:其实你多执行几次,会发现——**多个线程都在wait(),notify只会随机的唤醒一个 **

张小飞:好的,我再试试

8. notifyAll()

张小飞:有没有可以把所有都在 wait 中的线程,都唤醒呢?

诸小亮:如果想全部唤醒,可以使用notifyAll(),或者执行多次notify(),比如:

image.png

结果:

image.png

另外,执行多次notify()方法也可以,全部唤醒,比如:

image.png
结果:略

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();
    }
}

结果:

image.png

张小飞:这个代码能否具体解释一下?

诸小亮:当然可以

  首先创建一个名为 yase 的线程对象
  然后创建了 daji、lvbu 两个线程,这两个线程都把 yase 对象作为锁对象
	这两个线程分别启动后都执行 wait 方法,进入等待模式
  最后yase线程启动,执行run方法时,调用 this.notify();
  结果:daji、lvbu 都被唤醒

张小飞:这是什么原理?

诸小亮:额。。。。,很抱歉我也没仔细研究过,暂时记住这个现象吧

张小飞:好的

3. 生产消费模式

诸小亮:接着我们说一个——生产消费模式

张小飞:这是做什么的?

诸小亮:**生产者生产数据,消费者消费数据,**先看一张图

image.png

张小飞:您是先说,面包师生产面包,放到面包柜,然后销售员再取走面包吗?

诸小亮:是的,这就是我们生活中的——生产消费模式

诸小亮:工作中,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();
    }
}

结果中,两个线程互相唤醒,交替执行

image.png

诸小亮:怎么样,可以看懂吗?

张小飞:可以可以,原来这就是生产消费模式

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 状态(之后会详细解释线程状态)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值