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成长史3 分钟前
deepseek梳理java高级开发工程师微服务面试题-进阶版
java·spring cloud·微服务
zkmall13 分钟前
Java + 鸿蒙双引擎:ZKmall开源商城如何定义下一代B2C商城技术标准?
java·开源·harmonyos
陌路物是人非14 分钟前
uniapp取消浏览自动填充
java·服务器·uni-app
獨枭16 分钟前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
伍六星16 分钟前
maven和npm区别是什么
java·npm·maven
才知山高路远20 分钟前
Java - Junit框架
java·junit·log4j
维基框架21 分钟前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱21 分钟前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
关于不上作者榜就原神启动那件事22 分钟前
Java基础学习
java·开发语言·学习
Black_Cat_yyds1 小时前
设计杂谈-工厂模式
java·开发语言