Wait-Notify机制

文章目录

    • [1. 简介](#1. 简介)
    • [2. 相关API](#2. 相关API)
    • [3. wait notify的正确姿势](#3. wait notify的正确姿势)
    • [4. 总结](#4. 总结)

1. 简介

回顾Minitor锁的结构:

  • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间
  • BLOCKED线程会在Owner线程释放锁时唤醒
  • WAITING线程会在Owner调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争

2. 相关API

  1. Obj.wait():让进入object的Monitor的线程到WaitSet等待
  2. Obj.wait(long n):有时限的等待,到n毫秒后结束等待,或者在n毫秒内被唤醒
  3. Obj.notify():在object上正在waitSet等待的线程中挑哟个唤醒
  4. Obj.notifyAll():让object上正在waitSet等待的线程全部被唤醒

3. wait notify的正确姿势

  • sleep(long n)和wait(long n)的区别
  1. sleep是Thread的静态方法,而wait是Object的方法
  2. sleep不需要强制和synchronized配合使用,但wait需要
  3. sleep在睡眠的同时,不会释放对象锁,但wait在等待时会释放对象锁
  • step1

思考下面代码:

java 复制代码
@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    log.debug(("没烟,先歇一会!"));
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }
            }
        },"小南").start();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    log.debug("可以开始干活了!");
                }
            },"其它人").start();
        }
        new Thread(()->{
            //synchronized (room){
                hasCigarette=true;
                log.debug("烟到了哦!");
            //}
        },"送烟的").start();
    }
}

问题是,小南线程在睡眠的时候,其它线程全部在EntryList中阻塞等待,在高并发场景下,这种效率是很低的,所以我们需要改善这种情况。

  • Step 2

使用wait-notify解决上面问题

java 复制代码
@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }
            }
        },"小南").start();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    log.debug("可以开始干活了!");
                }
            },"其它人").start();
        }
        new Thread(()->{
            synchronized (room){
                hasCigarette=true;
                log.debug("烟到了哦!");
                room.notify();
            }
        },"送烟的").start();
    }
}

上面代码初步解决了前面小南线程在等待烟的时候,所有等待room的线程都会阻塞的问题。但同时也引入了新的问题,假如不止小南线程在等待,还有其它线程在wait,那么送烟线程会错误的唤醒其它线程,而不是指定的小南线程

  • Step 3
java 复制代码
@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }else {
                    log.debug("这活干不了!");
                }
            }
        },"小南").start();
        new Thread(()->{
            synchronized (room){
                log.debug("外买到了没?[{}]",hasTakeout);
                if(!hasTakeout){
                    try {
                        log.debug(("没外卖,先歇一会!"));
                        room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("外买到了没?[{}]",hasTakeout);
                if(hasTakeout){
                    log.debug("开始干活了!");
                }else{
                    log.debug("这活干不了!");
                }
            }
        },"小北").start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout=true;
                log.debug("外卖到了哦!");
                room.notify();
            }
        },"送外卖的").start();
    }
}

从上面结果我们可以看出,外卖的线程唤醒了小南线程,这就出现了虚假唤醒的情况,那么怎么解决这个问题呢,我们使用notifyAll可以解决上面问题。

java 复制代码
@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }else {
                    log.debug("这活干不了!");
                }
            }
        },"小南").start();
        new Thread(()->{
            synchronized (room){
                log.debug("外买到了没?[{}]",hasTakeout);
                if(!hasTakeout){
                    try {
                        log.debug(("没外卖,先歇一会!"));
                        room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("外买到了没?[{}]",hasTakeout);
                if(hasTakeout){
                    log.debug("开始干活了!");
                }else{
                    log.debug("这活干不了!");
                }
            }
        },"小北").start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout=true;
                log.debug("外卖到了哦!");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

虽然我们解决了小北的问题,小南的问题我们还是没有解决,由于小南被外卖线程给唤醒了,但是却没有拿到烟,这是小南线程还是没干活,这就是新出现的问题。

  • Step 4

我们之前使用的if判断,如果我们改成while循环就解决了上面问题

java 复制代码
@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                while(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }else {
                    log.debug("这活干不了!");
                }
            }
        },"小南").start();
        new Thread(()->{
            synchronized (room){
                log.debug("外买到了没?[{}]",hasTakeout);
                while(!hasTakeout){
                    try {
                        log.debug(("没外卖,先歇一会!"));
                        room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("外买到了没?[{}]",hasTakeout);
                if(hasTakeout){
                    log.debug("开始干活了!");
                }else{
                    log.debug("这活干不了!");
                }
            }
        },"小北").start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout=true;
                log.debug("外卖到了哦!");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

到此为止我们解决了虚假唤醒的问题

4. 总结

我们使用wait-notify的正确姿势应该如下:

java 复制代码
synchronized(lock){
	while(条件B不成立)
	{
       lock.wait();
	}
	//代码逻辑
}
synchronized(lock){
	lock.notifyAll();
}
相关推荐
开开心心就好11 分钟前
本地文件搜索工具,双击Ctrl就能呼出搜索
java·服务器·前端·支持向量机·ffmpeg·音视频·模拟退火算法
独隅16 分钟前
Claude 高效使用指南深度评测
java·开发语言·claude·claude code
ByteCoder.23 分钟前
班前十分钟的清单,按体检引导单来写
java·服务器·前端·助播虾
海的透彻25 分钟前
如何让docker容器启动时用自定义的用户
java·docker·dubbo
疯狂学习GIS30 分钟前
将谷歌地球引擎账号升级为Contributor与Partner层级的方法
java·数据库·人工智能
GG.WinGel41 分钟前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯
泡泡糖的中文规格书1 小时前
HDLbits 2.2 学习笔记 · 多路复用器
java·开发语言
John_Hui1 小时前
第 3 篇:配置 JDK 17 环境
java·开发语言·ubuntu
秋天的怀念_David1 小时前
算法一千零一夜 · 第十七夜 长街点灯
java·程序人生·算法
不吃鱼的羊1 小时前
如何应对恐惧
java