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();
}
相关推荐
IT爱学堂1 小时前
尚硅谷 - 2025年3月Java+AI大模型应用开发
java·开发语言·人工智能
小贤plus2 小时前
SpringBoot 三大核心注解精讲:@ControllerAdvice、@RestControllerAdvice、@Validated 分组校验(实战)
java
吠品2 小时前
Java byte数组与String互转:编码细节与踩坑记录
java·linux·服务器
晚风醉蝶2 小时前
1-6-插入排序-InsertionSort
java·数据结构·排序算法
yaoxin5211233 小时前
497. Java 反射 - 使用反射读取注解
java·开发语言·python
Lam Tang3 小时前
APS 系列文章 01
java·代理模式
eralong4 小时前
Java 面向对象:继承、多态、接口
java·后端
哦虎!4 小时前
【数据库】事务
java·数据库·mysql
CDN3604 小时前
流媒体加速实践:出海东南亚短视频点播频繁缓冲,HLS 分片与 Nginx 配置调优
java·网络·nginx·流媒体加速
程序员黑豆4 小时前
Java中的null与NullPointerException完全指南:安全处理、实战排查与面试题
java·前端·ai编程