线程学习(14)-wait notify notifyAll

本文详细解析了Java中wait、notify及notifyAll方法的工作原理及其与synchronized关键字的配合使用方式,通过实例演示了线程间的正确通信机制,并讨论了虚假唤醒的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

wait ,notify,notifyAll和原先学习的方法不同,这些是Object类中的方法,为什么是Object类中的方法,而不定义成线程中的方法。

当时面试的时候,面试官问了个这问题。网上找一下标准答案。

wait与notify,notifyAll是不仅是普通方法与同步机制,还是java两个线程中的通信机制。因为该通信机制与synchronized绑定,又需要保证对所有对象可用,所以放到Object中比较好。

每个对象都可以上锁,这也就是放到Object中,而不是放到Thread类中的原因。

在 Java 中,为了进入代码的临界区,线程需要锁定并等待锁,他们不知道哪些线程持有锁,而只是知道锁被某个线程持有, 并且需要等待以取得锁, 而不是去了解哪个线程在同步块内,并请求它们释放锁。

其实我觉得上面说的不太好,但让我总结个标准答案我也组织不好语言。反正在我的理解,wait,notify,notifyAll这些方法,是为了与synchronized配合使用,保证程序之间的指令执行次序,不会造成数据问题。要知道,线程进入临界区后,依赖的是锁资源主观层面来完成线程之间的阻塞,而不是在线程主观层面请求释放锁。锁是对象层面的东西,所以放到Object中。

总感觉不对劲,这什么sb问题。不想那么多,往下写。

底层原理

wait,notify,notifyAll都是结合synchronized来进行使用的,而wait状态的数据是存放到monitor中的waitSet中,所以调用这东西就直接重量级锁了。

流程如下:

1.当一个线程执行到某个阶段,不满足执行条件,调用wait方法,将当前线程放到waitSet中,成为waiting或者timed_waiting状态,并将Owner置为空,释放掉持有的锁资源。

2.从EntryList中唤醒线程,参与CPU时间片的竞争

3.当方法执行到当前锁资源的notify或notifyall方法,此时wait状态线程苏醒,放入至entryList中进行竞争,竞争成功后,线程继续执行,否则激素在EntryList中阻塞。

看一下代码吧。jiu就

package com.bo.threadstudy.four;

import lombok.extern.slf4j.Slf4j;

/**
 * 测试线程wait以及notify,内存通信
 */
@Slf4j
public class WaitNotifyTest {
    private static final Object lock = new Object();

    //先写个普通的方法吧,不考虑虚拟唤醒的情况
    public static void main(String[] args) {
        //没什么意思,初始简单版
        new Thread(() -> {
            log.debug("线程1在执行");
            synchronized (lock){
                lock.notify();
//                lock.notifyAll();
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (lock){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("线程2在执行");
        },"t2").start();

        new Thread(() -> {
            synchronized (lock){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("线程3在执行");
        },"t3").start();

    }

}

 很简单的一个唤醒机制,我刚才说了,notify或notifyAll方法会先将waiting或者timed_waiting状态的线程存放至entryList中,也就是blocking状态,看看是不是?

 

再往下执行一步

这里的monitor代表的就是阻塞,它成功进入了阻塞状态。

wait方法是将线程存放至waitSet中,而notify是随机唤醒一个线程进入entryList中进行等待,notifyAll是唤醒当前锁全部wait的线程。

notify的结果,有一个线程的结果阻塞住,出不来。

notifyAll的结果,两个wait的线程就全部唤醒了,将notify注释,放开notifyAll。

虚假唤醒问题

事实上,我现在有这么个场景,有个辅导老师,需要俩小伙A,B中某个人来填一个表格。模拟一下场景。

主线程作为老师,先搬一个凳子,然后开始叫人

小A和小B默认是等待状态,等待老师唤醒

什么是虚假唤醒,老师想唤醒B,但却不确定用notify具体会唤醒谁。所以用notifyAll?

好吧,notifyAll俩人都唤醒了,但是只需要一个人填表,这个时候,需要加个判定条件,老师意愿到底唤醒谁,另一个随机唤醒的只能回去阻塞。

代码如下:

package com.bo.threadstudy.four;

import lombok.extern.slf4j.Slf4j;

/**
 * 写一个例子来做虚假唤醒问题
 */
@Slf4j
public class WaitNotifyExampleTest {

    private static boolean aStatus = false;
    private static boolean bStatus = false;

    public static void main(String[] args) throws InterruptedException {
        log.debug("开始准备凳子");
        Thread.sleep(100);
        Object lock = new Object();
        log.debug("准备好了");

        new Thread(() -> {
            synchronized (lock){
                try {
                    while(!aStatus){
                        log.debug("小A正在等待");
                        lock.wait();
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("小A在填写表格");
                log.debug("填写表格完成");
            }
        },"小A").start();

        new Thread(() -> {
            synchronized (lock){
                try {
                    while(!bStatus){
                        log.debug("小B正在等待");
                        lock.wait();
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("小B在填写表格");
                log.debug("填写表格完成");
            }
        },"小B").start();
        
        synchronized (lock){
            bStatus = true;
            lock.notifyAll();
        }
    }

}

 如果不想阻塞的话,也可以采用中断操作interrute,来中断另一个等待线程,或者设置一个状态,在一个状态中设置另一个状态,让程序结束。

没事,后面写个练习试试。好好磨练一下。

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值