wait-nofity

简介: wait-nofity

一、原理

分析

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

二、API介绍

方法

说明

obj.wait()

wait方法让进入object监视器的线程到waitSet等待。wait后会释放对象锁,让其他线程竞争。

obj.wait(Long timeout)

限时等待。导致当前的线程等待,等待被其他线程唤醒,或者指定的时间timeout用完,线程不再等待。

obj.notify()

在 object 上正在waitSet

等待的线程中挑一个唤醒。

obj.notifyAll()

让 object 上正在 waitSet

等待的线程全部唤醒。

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

示例代码

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    final static Object obj = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (obj){
                log.debug("执行...");
                try {
                    obj.wait(); //让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码...");
            }
        },"t1").start();
        new Thread(() -> {
            synchronized (obj){
                log.debug("执行...");
                try {
                    obj.wait(); //让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码...");
            }
        },"t2").start();
        //主线程两秒后执行
        sleep(2);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
            obj.notify(); // 唤醒obj上一个线程
//            obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}
  • notify运行结果
22:36:43.159 c.TestWaitNotify [t1] - 执行...
22:36:43.161 c.TestWaitNotify [t2] - 执行...
22:36:45.171 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
22:36:45.171 c.TestWaitNotify [t1] - 其它代码...
  • notifyAll运行结果
22:39:49.383 c.TestWaitNotify [t1] - 执行...
22:39:49.385 c.TestWaitNotify [t2] - 执行...
22:39:51.389 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
22:39:51.389 c.TestWaitNotify [t2] - 其它代码...
22:39:51.389 c.TestWaitNotify [t1] - 其它代码...

wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到 notify 为止。

wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify。

三、wait notify的正确姿势

3.1 sleep(long n) 和 wait(long n) 的区别

  • sleep 是 Thread 的方法,而 wait 是 Object 的方法
  • sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  • sleep 在睡眠的同时,不会释放对象锁,但 wait 在等待的时候会释放对象锁
  • 无时限wait方法执行后线程变为WAITING状态,有时限的wait方法与sleep方法执行后变为TIMED_WAITING状态

3.2 演变步骤

step 1
@Slf4j(topic = "c.TestCorrectPostureStep1")
public class TestCorrectPostureStep1 {
    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("没烟,先歇会!");
                    sleep(2);
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if (hasCigarette){
                    log.debug("可以开始干活了");
                }
            }
        },"Leefs").start();
        for (int i = 0; i < 5; i++){
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }
        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了!");
            }
        },"送烟的").start();
    }
}

运行结果

22:50:16.626 c.TestCorrectPostureStep1 [Leefs] - 有烟没?[false]
22:50:16.628 c.TestCorrectPostureStep1 [Leefs] - 没烟,先歇会!
---------- 中间间隔2s -------
22:50:18.638 c.TestCorrectPostureStep1 [Leefs] - 有烟没?[false]
22:50:18.638 c.TestCorrectPostureStep1 [送烟的] - 烟到了!
22:50:18.638 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了
22:50:18.638 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了
22:50:18.638 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了
22:50:18.639 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了
22:50:18.639 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了

分析

  • 通过sleep方法来使线程处于等待状态;
  • 【Leefs线程】必须睡足2s后才能醒来,就算烟提前送到,也无法立刻醒来;
  • 如果送烟的线程也加了 synchronized (room) 后,就好比【Leefs】在里面反锁了门睡觉,烟根本没法送进门,main 没加synchronized 就好像 main 线程是翻窗户进来的;
  • 当【Leefs线程】获得锁在睡眠时,其它干活的线程,都要一直阻塞,效率太低;
  • 解决方法,使用 wait - notify 机制。
step 2
@Slf4j(topic = "c.TestCorrectPostureStep2")
public class TestCorrectPostureStep2 {
    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 {
                        room.wait(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if(hasCigarette){
                    log.debug("可以开始干活了");
                }
            }
        },"Leefs").start();
        for (int i = 0; i < 5; i++){
            new Thread(() -> {
                synchronized (room){
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }
        sleep(1);
        new Thread(() -> {
            synchronized (room){
                hasCigarette = true;
                log.debug("烟到了!");
                room.notify();
            }
        },"送烟的").start();
    }
}

运行结果

23:05:18.333 c.TestCorrectPostureStep2 [Leefs] - 有烟没?[false]
23:05:18.336 c.TestCorrectPostureStep2 [Leefs] - 没烟,先歇会!
23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了
23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了
23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了
23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了
23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了
23:05:19.342 c.TestCorrectPostureStep2 [送烟的] - 烟到了!
23:05:19.343 c.TestCorrectPostureStep2 [Leefs] - 有烟没?[true]
23:05:19.343 c.TestCorrectPostureStep2 [Leefs] - 可以开始干活了
  • 解决了当【Leefs线程】处于睡眠状态时,其他线程可以获取锁不需等待释放锁的问题
  • 但如果有其它线程也在等待条件呢?送烟的主线程notify会不会错误地叫醒其他线程呢?
step 3
@Slf4j(topic = "c.TestCorrectPostureStep3")
public class TestCorrectPostureStep3 {
    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 {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        },"Leefs").start();
        sleep(1);
        new Thread(() -> {
            synchronized (room){
                log.debug("外卖送到没?[{}]",hasTakeout);
                if(!hasTakeout){
                    log.debug("没外卖,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        },"Jeyoo").start();
        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖送到了!");
                room.notify();
            }
        },"送外卖的").start();
    }
}

运行结果

23:12:26.760 c.TestCorrectPostureStep3 [Leefs] - 有烟没?[false]
23:12:26.762 c.TestCorrectPostureStep3 [Leefs] - 没烟,先歇会!
23:12:27.766 c.TestCorrectPostureStep3 [Jeyoo] - 外卖送到没?[false]
23:12:27.766 c.TestCorrectPostureStep3 [Jeyoo] - 没外卖,先歇会!
23:12:28.771 c.TestCorrectPostureStep3 [送外卖的] - 外卖送到了!
23:12:28.772 c.TestCorrectPostureStep3 [Leefs] - 有烟没?[false]
23:12:28.773 c.TestCorrectPostureStep3 [Leefs] - 没干成活...
  • notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】
  • 解决方法,改为 notifyAll
step 4
@Slf4j(topic = "c.TestCorrectPostureStep4")
public class TestCorrectPostureStep4 {
    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 {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        },"Leefs").start();
        sleep(1);
        new Thread(() -> {
            synchronized (room){
                log.debug("外卖送到没?[{}]",hasTakeout);
                if(!hasTakeout){
                    log.debug("没外卖,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        },"Jeyoo").start();
        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖送到了!");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

运行结果

10:42:37.796 c.TestCorrectPostureStep5 [Leefs] - 有烟没?[false]
10:42:37.798 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会!
10:42:38.801 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[false]
10:42:38.801 c.TestCorrectPostureStep5 [Jeyoo] - 没外卖,先歇会!
10:42:39.809 c.TestCorrectPostureStep5 [送外卖的] - 外卖送到了!
10:42:39.810 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[true]
10:42:39.810 c.TestCorrectPostureStep5 [Jeyoo] - 可以开始干活了
10:42:39.810 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会!
  • 用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了
  • 解决方法,用 while + wait,当条件不成立,再次 wait
step 5

将 if 改为 while

if (!hasCigarette) {
    log.debug("没烟,先歇会!");
    try {
        room.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

改动后

  • wait方法被唤醒后才会执行后面代码,因此此处不会导致while循环空转
while (!hasCigarette) {
    log.debug("没烟,先歇会!");
    try {
        room.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

运行结果

23:20:07.016 c.TestCorrectPostureStep5 [Leefs] - 有烟没?[false]
23:20:07.019 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会!
23:20:08.030 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[false]
23:20:08.031 c.TestCorrectPostureStep5 [Jeyoo] - 没外卖,先歇会!
23:20:09.033 c.TestCorrectPostureStep5 [送外卖的] - 外卖送到了!
23:20:09.034 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[true]
23:20:09.034 c.TestCorrectPostureStep5 [Jeyoo] - 可以开始干活了
23:20:09.034 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会!

总结:正确姿势

synchronized(lock) {
  while(条件不成立) {
    lock.wait();
  }
  // 干活
}
//另一个线程
synchronized(lock) {
  lock.notifyAll();
}
目录
相关文章
|
6月前
|
弹性计算 运维 Shell
wait命令
【4月更文挑战第29天】
42 2
|
存储 缓存 安全
sleep () 和 wait () 的区别
sleep () 和 wait () 的区别
82 0
|
Java 程序员
sleep 和 wait 的区别
Java 中,线程的 "sleep" 和 "wait" 方法区别
123 0
|
监控
实践解读CLOSE_WAIT和TIME_WAIT
实践解读CLOSE_WAIT和TIME_WAIT
376 0
实践解读CLOSE_WAIT和TIME_WAIT
线程 - wait & sleep 区别
线程 - wait & sleep 区别
117 0
|
Java
sleep与wait区别
第一个区别是在对系统资源的占用上。 wait是Object类的一个函数(也就意味着所有对象都有这个函数),指线程处于进入等待状态,此时线程不占用任何资源,不增加时间限制。wait可以被notify和notifyAll函数唤醒(当然这两个同时也是Object的函数)。 而sleep则是Thread类的一个函数,指线程被调用时,占着CPU不工作。此时,系统的CPU部分资源被占用,其他线程无法进入,会增加时间限制。
140 0
|
监控
sleep 与 wait 区别
sleep 与 wait 区别
113 0
wait 和 notify 有坑。。
也许我们只知道wait和notify是实现线程通信的,同时要使用synchronized包住,其实在开发中知道这个是远远不够的。接下来看看两个常见的问题。