wait notify使用及其原理

1、API介绍

  • obj.wait() 让进入 object 监视器的线程到 waitSet 等待
  • obj.wait(long timeout) 让进入 object 监视器的线程到 waitSet 等待,但是会有一个时限,不会无限制等待
  • obj.notify()object 上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll()object 上正在 waitSet 等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁(成为 Owner),才能调用这几个方法

比如:

private final static Object OBJ = new Object();

@SneakyThrows
public static void main(String[] args) {
    OBJ.wait();
}

image-20220208174559897

报错误的监视器状态异常,也就是当前线程压根就没获取到 OBJ 锁,执行 wait 不知道去哪个 WaitSet?正确应该这样用

private final static Object OBJ = new Object();

@SneakyThrows
public static void main(String[] args) {
    synchronized (OBJ) {
        OBJ.wait();
    }
}

示例

private final static Object OBJ = new Object();

@SneakyThrows
public static void main(String[] args) {
    new Thread(() -> {
        synchronized (OBJ) {
            log.info("执行....");
            try {
                OBJ.wait(); // 让线程在OBJ上一直等待下去
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("t1 其它代码....");
        }
    }, "t1").start();
    new Thread(() -> {
        synchronized (OBJ) {
            log.info("执行....");
            try {
                OBJ.wait(); // 让线程在OBJ上一直等待下去
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("t2 其它代码....");
        }
    }, "t2").start();
    // 主线程两秒后执行
    TimeUnit.SECONDS.sleep(2);
    log.info("唤醒 OBJ 上其它线程");
    synchronized (OBJ) {
        OBJ.notify(); // 唤醒OBJ上一个线程
    }
}

image-20220208180212179

可以发现,notify 一次只能唤醒 WaitSet 中的随机一个,如果需要将 t1 和 t2 同时唤醒,可以使用 notiyAll,将所有的 WaitSet 中的线程都唤醒

2、原理

回顾下面这张图

image-20220208173157829

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

3、wait和sleep的区别

  • sleepThread 方法,而 waitObject 的方法
  • sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  • sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  • 它们状态都是 TIMED_WAITING

4、正确使用姿势

这里模拟一个场景,现在有一个工作室(锁),有一个人小南,他毛病比较多,它必须有烟才能干活,我们实现一下

static final Object ROOM = new Object();
    static boolean hasCigarette = false;

    @SneakyThrows
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (ROOM) {
                log.info("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.info("没烟,先歇会!");
                    try {
                        ROOM.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.info("可以开始干活了");
                }
            }
        }, "小南").start();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (ROOM) {
                    log.info("可以开始干活了");
                }
            }, "其它人").start();
        }
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            synchronized (ROOM) {
                ROOM.notify();
                hasCigarette = true;
                log.info("烟到了噢!");
            }
        }, "送烟的").start();
    }

image-20220208190942675

但是现在的程序还存在一个问题,如果有其他线程也在 wait 怎么办呢?岂不是容易唤醒错误的线程(虚假唤醒)?比如,现在还有一个人叫小女,她也很奇怪,只有外卖到了他才能干活

static final Object ROOM = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;

@SneakyThrows
public static void main(String[] args) {
    new Thread(() -> {
        synchronized (ROOM) {
            log.info("有烟没?[{}]", hasCigarette);
            if (!hasCigarette) {
                log.info("没烟,先歇会!");
                try {
                    ROOM.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("有烟没?[{}]", hasCigarette);
            if (hasCigarette) {
                log.info("可以开始干活了");
            } else {
                log.info("没干成活...");
            }
        }
    }, "小南").start();
    new Thread(() -> {
        synchronized (ROOM) {
            Thread thread = Thread.currentThread();
            log.info("外卖送到没?[{}]", hasTakeout);
            if (!hasTakeout) {
                log.info("没外卖,先歇会!");
                try {
                    ROOM.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("外卖送到没?[{}]", hasTakeout);
            if (hasTakeout) {
                log.info("可以开始干活了");
            } else {
                log.info("没干成活...");
            }
        }
    }, "小女").start();
    TimeUnit.SECONDS.sleep(1);
    new Thread(() -> {
        synchronized (ROOM) {
            hasTakeout = true;
            log.info("外卖到了噢!");
            ROOM.notify();
        }
    }, "送外卖的").start();
}

image-20220208191307200

为了解决虚假唤醒,导致正确的线程没有被唤醒,我们可以尝试使用 notifyAll ,这样正确的目标线程一定能被唤醒,但是也会唤醒不该唤醒的线程,所以此时,我们可以尝试使用 while 循环来判断是否是有效唤醒,否则再次 wait

static final Object ROOM = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;

@SneakyThrows
public static void main(String[] args) {
    new Thread(() -> {
        synchronized (ROOM) {
            log.info("有烟没?[{}]", hasCigarette);
            while (!hasCigarette) {
                log.info("没烟,先歇会!");
                try {
                    ROOM.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("有烟没?[{}]", hasCigarette);
            if (hasCigarette) {
                log.info("可以开始干活了");
            } else {
                log.info("没干成活...");
            }
        }
    }, "小南").start();
    new Thread(() -> {
        synchronized (ROOM) {
            log.info("外卖送到没?[{}]", hasTakeout);
            while (!hasTakeout) {
                log.info("没外卖,先歇会!");
                try {
                    ROOM.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("外卖送到没?[{}]", hasTakeout);
            if (hasTakeout) {
                log.info("可以开始干活了");
            } else {
                log.info("没干成活...");
            }
        }
    }, "小女").start();
    TimeUnit.SECONDS.sleep(1);
    new Thread(() -> {
        synchronized (ROOM) {
            hasTakeout = true;
            log.info("外卖到了噢!");
            ROOM.notifyAll();
        }
    }, "送外卖的").start();
    TimeUnit.SECONDS.sleep(1);
    new Thread(() -> {
        synchronized (ROOM) {
            ROOM.notify();
            hasCigarette = true;
            log.info("烟到了噢!");
        }
    }, "送烟的").start();
}

image-20220208191951143

所以正确的使用模板应该是

synchronized(lock) {
    while(条件不成立) {
        lock.wait();
    }
    // 干活
}
//另一个线程
synchronized(lock) {
    lock.notifyAll();
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值