你真的会正确使用wait和notify么?

news2024/12/26 1:01:45

目录

wait和notify原理

API

wait 与 sleep的区别

wait 和 notify的正确使用

step1

step2

step3

step4

step5

总结wait+notify


wait和notify原理

  • 当我们线程获取某个对象的monitor锁的时候就会成为owner线程,当owner线程条件不满足的时候,就会调用wait方法,该线程就会进入这个monitor里的waitSet里面等待(waitSet里面的线程都是当条件不满足时处于等待状态的线程),线程状态也就从运行状态(RUNNABLE) --> 阻塞状态(WAITING),如果调用的是无参的wait就会一直在WaitSet中等待,而那些没有获取到锁的线程就会进入到monitor的entrylist中进行阻塞等待,线程状态为BLOCKED

虽然处于waitSet和EntryList中的线程状态都是阻塞状态,不会占用CPU时间片.但是两者具有很大的区别

  • WaitSet里面的线程是因为条件不满足处于阻塞状态,线程已经获得了锁,但又释放了锁,线程状态为WAITING
  • EntryList里面的线程是因为没有竞争到锁而处于阻塞状态,线程状态为BLOCKED

WaitSet中的线程什么时候会被唤醒呢 ?

当owner线程调用notify或者notifyAll方法的时候就会唤醒在WaitSet中等待的线程,唤醒的线程就会进入EntryList中重新竞争锁,而不是立即获取到锁.

EntryList中的线程什么时候被唤醒呢 ?

当Owner线程释放锁的时候就会唤醒EntryList中的线程,在EntryList中的线程就会竞争成为Owner.(竞争锁)

wait的本意就是条件不满足释放锁并且自己进入WaitSet中等待,让其他线程有机会获取锁,

API

  • obj.wait() 是让进入object监视器的线程到waitSet中等待
  • obj.notify() 是在object上正在waitSet等待的线程随机挑一个来唤醒
  • obj.notifyAll() 是让object上正在WaitSet中等待的线程全部唤醒

这些方法都是线程之间进行通信使用的,都属于Object对象的方法. 使用wait notify/notifyAll 必须要获得此对象的锁,才可以调用它们.

演示一下 wait和notify

@Slf4j(topic = "c.demo1")
public class TestWaitNotify {
    private static final Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (obj) {
                log.debug("执行...");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码");
            }
        },"t1").start();

        new Thread(()->{
            synchronized (obj) {
                log.debug("执行...");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码");
            }
        },"t2").start();

        Thread.sleep(1000);
        System.out.println("唤醒obj中的线程");
        synchronized (obj) {
            obj.notify();//唤醒其中一个线程
            //obj.notifyAll();//唤醒在waitSet中的所有线程
        }
    }
}

调用notify

首先线程调度器会给随机一个线程分配CPU时间片,这里线程1先被调度,线程内部 先会获取obj对象的锁->关联上monitor,然后调用obj.wait()方法,也就会释放锁,然后进入monitor的waitSet中等待,接着轮到线程2执行,同样线程2也会先获取到obj对象锁,然后调用wait(),释放锁,进入WaitSet中等待. 1s之后 主线程也获取到obj对象的锁,会执行obj.notify(),就会随机唤醒一个在WaitSet中等待的线程.

调用notifyAll()

对于notifyAll,是会随机唤醒所有在WaitSet中等待的线程.

wait(long timeout)

wait无参数的方法就是使用wait(0)来实现的

wait(long timeout) 是等待一段timeout时长的时间,如果还没有被唤醒,就继续往下执行.

如果被提前唤醒,也是会继续往下执行.(提前执行)

wait 与 sleep的区别

  • sleep是Thread类的静态方法,而wait是所有对象都有的方法,是Object的方法

  • sleep 不需要强制与 synchronized 配合使用 , 但是 wait 必须和 synchronized 一起使用

  • sleep 在睡眠的同时不会释放对象锁, 但是 wait在等待的时候会释放对象锁
//锁对象建议使用final修饰的,因为锁对象不可以变,如果变了,锁的就不是相同的对象了
    private static final Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (obj) {
                log.debug("获得锁");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        Thread.sleep(1000);
        synchronized (obj) {
            log.debug("获得锁");
        }
    }

sleep方法在执行的同时不会释放锁,会一直等待线程睡眠完毕

wait方法

加了wait方法之后,当调用wait方法的时候会释放锁,这样1s后主线程就获得了锁.所以wait方法在等待的时候会释放对象锁

两者共同点就是 : 线程的状态都为TIMEDWAITING

wait() 为 WAITING wait(long timeout) TIMEDWAITING

wait 和 notify的正确使用

一共分为5个步骤进行讲解,每一步都对上一步做的不好的地方做出改进,然后最终改成wait,notify的标准正确使用

场景 : 有一个人小南和5个人要去房间里面干活,但是小南必须要烟才能干活,否则干不了,所以还会有一个线程来给小南送烟.

step1

怎么能让小南,和5个人达到最高的效率干活呢? 不能让他们摸鱼否则工资白给他们了.

@Slf4j(topic = "c.TestWaitNotify1")
public class TestWaitNotify1 {
    private static final Object room = new Object();
    private static boolean isCigarette = false;
    public static void main(String[] args) throws InterruptedException {
        //小南等烟
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有香烟 : " +isCigarette);
                if(!isCigarette){
                    log.debug("没有香烟,干不了活,等一会(2s)");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("是否有香烟 : " +isCigarette);
                if(isCigarette){
                    log.debug("小南开始干活");
                }
            }
        },"小南").start();

        //还有5个人也要去房间干活
        for(int i =0;i<5;++i){
            new Thread(()->{
               synchronized (room){
                   log.debug("开始干活");
               }
            },"其他人" + i).start();
        }

        Thread.sleep(1000);
        //还有一个线程来送烟
        new Thread(()->{
            synchronized (room){
                isCigarette = true;
                log.debug("送烟送到了");
            }
        },"送烟的").start();
    }
}

实现了如上代码:

  • 有一个小南线程,要进入房间干活(关联monitor->获取锁),但是要想让它干活必须要等烟,等烟到了之后他就会干活,如果烟还没有到,就是用sleep睡眠2s
  • 还有5个人也要去同一个room房间干活
  • 最后一个是送烟线程,1s之后他会给小南送烟,烟送到了小南就可以干活了.

执行一下看结果 :

缺点 :

  • 在小南阻塞的同时其他线程也在阻塞也干不了活.--->可以考虑使用wait,notify(wait可以释放锁)
  • 小南没有烟就一直在房间等着,就算送烟的提前把烟给小南送到也是不能立马干活的.(--->可以考虑使用wait,notify(wait可以释放锁))

送烟线程到底加不加synchronized

  1. 送烟的线程如果加了synchronized(room),就好像小南反锁了门,只要小南一直在房间,别人怎么都进不去.
  2. 送烟线程如果没有加synchronized(room),就好像送烟的人跳窗户,把烟送来的.

step2

@Slf4j(topic = "c.TestWaitNotify2")
public class TestWaitNotify2 {
    private static final Object room = new Object();
    private static boolean isCigarette = false;
    public static void main(String[] args) throws InterruptedException {
        //小南等烟
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有香烟 : " +isCigarette);
                if(!isCigarette){
                    log.debug("没有香烟,干不了活,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {//加了异常是因为有可能被interrupt打断
                        e.printStackTrace();
                    }
                }
                log.debug("是否有香烟 : " +isCigarette);
                if(isCigarette){
                    log.debug("小南开始干活");
                }
            }
        },"小南").start();

        //还有5个人也要去房间干活
        for(int i =0;i<5;++i){
            new Thread(()->{
                synchronized (room){
                    log.debug("开始干活");
                }
            },"其他人" + i).start();
        }

        Thread.sleep(1000);
        //还有一个线程来送烟
        new Thread(()->{
            synchronized (room){
                isCigarette = true;
                log.debug("送烟送到了");
                room.notify();
            }
        },"送烟的").start();
    }
}

如上代码对step1进行了改进,加了wait,notify机制.-->将sleep换成了wait+notify

因为前面也讲了,调用wait方法时候,线程可以释放锁-->这样当小南线程阻塞的时候,其他线程也可以干活了-->其他线程不用阻塞了.

我们看一下执行结果 :

step3

虽然上面step2解决了[小南在阻塞时,其他线程可以正常工作了] 但是如果有多个线程在等待呢.可能又会不一样了.

这时给场景再加一下,在上面的基础上再加一个小女线程也要干活,但是她必须等到外卖在能继续干活.

@Slf4j(topic = "c.TestWaitNotify3")
public class TestWaitNotify3 {
    private static final Object room = new Object();
    private static boolean isCigarette = false;
    private static boolean isTakeOut = false;
    public static void main(String[] args) throws InterruptedException {
        //小南等烟
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有香烟 : " +isCigarette);
                if(!isCigarette){
                    log.debug("没有香烟,干不了活,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("是否有香烟 : " +isCigarette);
                if(isCigarette){
                    log.debug("小南开始干活");
                }else {
                    log.debug("小南没有干活");
                }
            }
        },"小南").start();

        //小女等外卖
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有外卖 : " +isTakeOut);
                if(!isTakeOut){
                    log.debug("没有外卖,干不了活,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("是否有外卖 : " +isTakeOut);
                if(isTakeOut){
                    log.debug("小女干活");
                }else {
                    log.debug("小女没有干活");
                }
            }

        },"小女").start();

        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                isTakeOut = true;
                log.debug("外卖到了");
                room.notify();
            }
        },"送外卖的").start();
    }
}

对于上面的代码为了简便代码,就把那5个人去掉了,然后先把送烟的线程去掉换成送外卖的线程,这样更能看出问题,然后加了一个小女线程她要等到外卖才能干活.

我们看一下执行结果:

再加一个等待的线程,就会出现虚假唤醒问题(本来叫醒的是A线程,但是唤醒的却是B线程),原因是notify唤醒的是WaitSet中随机的一个线程.

step4

为了解决虚假唤醒的问题,就在上个代码的基础上加上notifyAll,这样所有线程都会被唤醒.

@Slf4j(topic = "c.TestWaitNotify3")
public class TestWaitNotify3 {
    private static final Object room = new Object();
    private static boolean isCigarette = false;
    private static boolean isTakeOut = false;
    public static void main(String[] args) throws InterruptedException {
        //小南等烟
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有香烟 : " +isCigarette);
                if(!isCigarette){
                    log.debug("没有香烟,干不了活,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("是否有香烟 : " +isCigarette);
                if(isCigarette){
                    log.debug("小南开始干活");
                }else {
                    log.debug("小南没有干活");
                }
            }
        },"小南").start();

        //小女等外卖
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有外卖 : " +isTakeOut);
                if(!isTakeOut){
                    log.debug("没有外卖,干不了活,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("是否有外卖 : " +isTakeOut);
                if(isTakeOut){
                    log.debug("小女干活");
                }else {
                    log.debug("小女没有干活");
                }
            }

        },"小女").start();

        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                isTakeOut = true;
                log.debug("外卖到了");
                room.notifyAll();
            }
        },"送外卖的").start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                isCigarette = true;
                log.debug("烟到了");
                room.notifyAll();
            }
        },"送烟的").start();
    }
}

我又在上面的代码加上个送烟的线程,以便跟好的发现问题

看运行结果

这就发现问题了,小南烟最后送到了,只不过是完一会送到了,但还是没有干上活,让它摸鱼了.

原因就是小南只等待一次,送外卖的线程把小南也叫醒了,但是小南线程只if等待->等待一次,时间过了就不等了,还是没让他干上活. 还是没有彻底解决虚假唤醒问题

step5

notifyAll只解决了某个线程唤醒的问题.

要想真正彻底的解决虚假唤醒问题.我们只要使用while(){wait()},只要条件不成立就让他一直等.就彻底解决了虚假唤醒问题.

@Slf4j(topic = "c.TestWaitNotify4")
public class TestWaitNotify4 {
    private static final Object room = new Object();
    private static boolean isCigarette = false;
    private static boolean isTakeOut = false;
    public static void main(String[] args) throws InterruptedException {
        //小南等烟
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有香烟 : " +isCigarette);
                while(!isCigarette){
                    log.debug("没有香烟,干不了活,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("是否有香烟 : " +isCigarette);
                if(isCigarette){
                    log.debug("小南开始干活");
                }else {
                    log.debug("小南没有干活");
                }
            }
        },"小南").start();

        //小女等外卖
        new Thread(()->{
            synchronized (room) {
                log.debug("是否有外卖 : " +isTakeOut);
                if(!isTakeOut){
                    log.debug("没有外卖,干不了活,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("是否有外卖 : " +isTakeOut);
                if(isTakeOut){
                    log.debug("小女干活");
                }else {
                    log.debug("小女没有干活");
                }
            }

        },"小女").start();

        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                isTakeOut = true;
                log.debug("外卖到了");
                room.notifyAll();//解决部分线程虚假唤醒问题
            }
        }).start();
    }
}

这回加了个while(不成立){一直等着} ,就解决了虚假唤醒问题

看执行结果:

总结wait+notify

synchronized(lock){//lock锁对象
    while(条件不成立){//条件不成立就一直等着
        lock.wait();
    }
    //符合条件执行业务代码....
}
//这边唤醒的线程要用notifyAll
synchronized(lock){
    lock.notifyAll();
}

上面就解决了虚假唤醒问题,以及能够让阻塞的同时,其他线程可以干活,在一定的时间内,可以高效的完成任务.

以后在使用wait+notify的时候,就使用这么一套模板就行.已使用wait+notify就立马想到这套模板.

参考黑马程序员视频

01.001-为什么学习并发_哔哩哔哩_bilibili

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/132592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

惠州市政企信息化(互联网)市场调研报告

1.引言 1.1.编写目的 据广东省惠州市惠东县的政企信息化市场调研的客观性数据&#xff0c;分析相关数据&#xff0c;确定市场规模、市场潜力、市场需求&#xff0c;以及需求价值&#xff0c;为后续的市场决策、服务组合决策提供依据&#xff0c;也作为未来根据市场变化而调整…

Nacos 注册中心

Nacos 注册中心 目录概述需求&#xff1a;设计思路实现思路分析1.增加 Maven 依赖2.Client端配置注册中心3.Server端配置注册中心4.Nacos 注册中心参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c…

Java--Map接口详解

目录 Map接口的特点 代码实现 代码实现 Map的常用方法 代码实现 Map接口的4种遍历方法 代码实现 第一种方式 第二种方式 第三种方式 第四种方式 Map接口的特点 1)Map与Collection并列存在。用于保存具有映射关系的数据&#xff1a;Key-Value 2)Map中的key和value可以…

如何在星巴克连接家中Windows台式机?(安卓,iOS, Windows, macOS配合frp穿透公网IP实现)

zhaoolee 最近热衷于和海外热心老哥们交换硬盘中的单机游戏资源&#xff08;BT下载&#xff09;&#xff0c;家中有Windows台式机&#xff0c; 适合长时间挂机下载BT资源&#xff0c;zhaoolee希望能随时连接到Windows台式机新增下载任务&#xff0c;安装体积超大的主机游戏。 …

End-to-End Object Detection with Transformers论文阅读笔记

End-to-End Object Detection with Transformers 端到端&#xff0c;不需要NMS后处理了&#xff0c;直接出结果。 1、Abstract 将目标检测作为一个集合预测问题来解决。简化了检测的整体流程&#xff0c;有效的消除了许多人工设计的部分&#xff0c;比如NMS&#xff0c;anch…

数据库连接池(C++11实现)

目的&#xff1a; 因为对数据库的操作实质上是对磁盘的IO操作&#xff0c;所以如果对数据库访问次数过多&#xff0c;就会到导致大量的磁盘IO&#xff0c;为了提高MySQL数据库&#xff08;基于C/S设计&#xff09;的访问瓶颈&#xff0c;除了在服务器端增加缓存服务器缓存常用的…

还在用BERT做文本分类?分享一套基于预训练模型ERNIR3.0的文本多分类全流程实例【文本分类】

目录&#x1f340;一、前言&#x1f331;二、多分类场景简介&#x1f343;三、前期准备阶段&#x1f7e5;3.1 运行环境准备&#x1f7e7;3.2 文心ERNIE系列模型介绍&#x1f7e8;3.3 预训练模型加载⬜3.4 加载项目代码&#x1f490;四、数据准备阶段&#x1f7e9;4.1 数据处理流…

变不可能为可能——记房产推销员佟鑫海

有勤奋&#xff0c;就会有所收获。傲人的成绩和背后的努力密切相关。俗话说得好&#xff0c;没卖不掉的房子&#xff0c;仅有卖不掉房子的艺人经纪人。关键是你是否有恒心。 在明升&#xff0c;总会有这样一群影子&#xff0c;他们每天精力旺盛&#xff0c;衣着光鲜&#xff0…

【C/C++ SOCKET编程】基于TCP协议实现服务器客户端的简单通信

什么是SOCKET Socket又称"套接字"&#xff0c;应用程序通常通过"套接字"向网络发出请求或者应答网络请求&#xff0c;使主机间或者一台计算机上的进程间可以通讯。 TCP/IP协议 从字面意义上讲&#xff0c;有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议…

unsafe.Pointer和uintptr的区别

unsafe 包 func Alignof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Sizeof(x ArbitraryType) uintptr type ArbitraryType int type Pointer *ArbitraryType在unsafe包中&#xff0c;只提供了3个函数&#xff0c;两个类型。就这么少的量&#xf…

【数据结构进阶】布隆(Bloom Filter)过滤器【哈希+位图的整合】

布隆(Bloom Filter)过滤器【哈希位图的整合】 1、什么是布隆过滤器&#xff1f; 布隆过滤器&#xff08;Bloom Filter&#xff09;是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空…

物联网与射频识别技术,课程实验(四)

实验4—— 基于帧的时隙ALOHA(FSA)算法的实现与性能分析 实验说明&#xff1a; 利用Python或Matlab模拟基于帧的时隙ALOHA算法&#xff1b; 分析标签数量k、帧中所含时隙个数n对信道利用率的影响&#xff0c;其中&#xff0c; 信道利用率发送数据的时间/(发送数据的时间信道空…

【JavaEE】线程的状态转换

新年快乐! 祝新的一年万事胜意! 魅力无限! 随心所欲! 蒸蒸日上! 文章目录1. 线程的基本状态2.Java中线程的状态3. 线程的转换1. 线程的基本状态 操作系统中线程有三个基本状态,就绪状态,运行状态,阻塞状态. 就绪状态, 已经获得除CPU之外的所有资源,只要得到CPU,可立即执行. …

(二十五)大白话数据库无法连接故障的定位,Too many connections

文章目录 1、你是否遇到过Too many connections?2、linux的文件句柄数量被限制1、你是否遇到过Too many connections? 今天要给大家分析另外一个真实的大家都经常会碰到的数据库生产故障,就是数据库无法连接的问题。 大家会看到的异常信息往往是“ERROR 1040(HY000): Too …

ubuntu18.04下mysql数据库安装和C语言连接操作

数据库在应用系统开发中很常见&#xff0c;在众多的数据库中&#xff0c;mysql总是会占有一席之地。本篇说明一下如何在ubuntu18.04上安装mysql数据库。 目录 1.更新环境 2.安装mysql数据库系统 3.检测是否安装成功 4.启动、重启、关闭&#xff0c;删除 5.给root用户设置…

vue3+Ts使用vuex模块化和非模块化管理的2种方式(非pinia)

官网写的很清楚&#xff1a;https://vuex.vuejs.org/zh/guide/typescript-support.html 2种方式 (都不是使用pinia的) 1&#xff1a;复杂版本(不定义自己的 useStore 组合式函数) 使用的时候需要在vuex引入 useStore 在store文件引入导出的key import { useStore } from ‘vu…

CSS3新增的has伪类选择器,让你能轻松选择父元素

文章目录一、语法二、链式操作三、兼容性问题CSS现在新增了一个允许我们选择父元素的伪类&#xff1a;has选择器。可以将其当做父级选择器。 一、语法 选择器1:has(选择器2){} /* 表示选择包含有选择器2的所有的选择器1 比如&#xff1a;*/ div:has(p) {background: black; }…

计算机网络期末考试重点归纳

第 1 章 概述 1. 网络的基本特点 连通性共享性 2. internet 和 Internet 的含义 internetInternet中文名称互连网互联网/因特网名词性质通用名词专用名词名词解释指由多个计算机网络互连而成的计算机网络指当前全球最大的、开放的、由众多网络连接而成的特定互连网&#xff…

电子学会2020年6月青少年软件编程(图形化)等级考试试卷(一级)答案解析

目录 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题&#xff08;共3题&#xff0c;每题10分&#xff0c;共30分&#xff09; 青少年软件编程…

Ubuntu22.04下安装MongoDB(6.0版本)并进行相关数据库操作

前言 昨天用ubuntu22.04安装redis-5.0.5服务&#xff0c;因为版本不兼容&#xff0c;导致问题频发&#xff0c;最终在老师帮助下解决了&#xff0c;这又一次提醒了版本兼容的重要性 MongoDB安装与部署 因为所用ubuntu版本为22.04&#xff0c;所以不能按照老师所给文档进行安…