Wait-Notify机制

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

思考下面代码:

@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解决上面问题

@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
@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可以解决上面问题。

@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循环就解决了上面问题

@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的正确姿势应该如下:

synchronized(lock){
	while(条件B不成立)
	{
       lock.wait();
	}
	//代码逻辑
}
synchronized(lock){
	lock.notifyAll();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值