【Java深入学习】wait和notify源码及易错点-上

85 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

基本介绍

我们之前学习的过程中浅显的了解过wiat/notify,但是没有系统的介绍过wait/notify,wait是使线程陷入等待 notify是随机唤醒一个被wait的线程。

原理

当一个线程获取锁后 但是发现自己不满足某些条件 不能执行锁住部分的代码块时 需要进入等待列表 直到满足条件时才会重新竞争线程

注意:

当一个线程获取锁后 但是发现自己不满足某些条件 不能执行锁住部分的代码块时 需要进入等待列表 直到满足条件时才会重新竞争线程

API介绍

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

obj.wait(Long timeout):wait的有时限方法,如果在时限内没有其他线程唤醒,则自己直接唤醒自己,若期间有别的线程唤醒那就正常唤醒。wait后会释放对象锁,让其他线程竞争

obj.notify():notify方法让正在waitSet等待的线程挑一个唤醒

obj.notifyAll():notifyAll方法让正在waitSet等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于Object对象方法,必须获取此对象的锁,才能调用这几个方法,如果不加锁直接调用这些方法会报错

notify与notifyAll的对比

@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(0.5);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
//            obj.notify(); // 唤醒obj上一个线程
            obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}

结果:

调用notify时:

  • 23:11:55.798 c.TestWaitNotify [t1] - 执行…
  • 23:11:55.801 c.TestWaitNotify [t2] - 执行…
  • 23:11:56.300 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
  • 23:11:56.300 c.TestWaitNotify [t1] - 其它代码…

调用notifyAll时:

  • 23:12:26.195 c.TestWaitNotify [t1] - 执行…
  • 23:12:26.198 c.TestWaitNotify [t2] - 执行…
  • 23:12:26.699 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
  • 23:12:26.699 c.TestWaitNotify [t2] - 其它代码…
  • 23:12:26.699 c.TestWaitNotify [t1] - 其它代码… 解释:

可以看出notify是随机唤醒一个线程,notifyAll则是唤醒全部线程

ait与sleep方法的区别

区别

1.sleep是Thread的类方法,而wait是Object的对象方法

2.sleep不需要强制和synchronized配合使用,但是wait需要和synchronized一起用

3.sleep在睡眠的同时,不会释放对象锁,但wait在等待时会释放对象锁

4.无时限wait方法执行后 线程变为WAITING状态,有时限的wait方法与sleep方法执行后变为TIMED_WAITING状态

分析下面代码

@Slf4j(topic = "c.Test19")
public class Test19 {

    static final Object lock = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                log.debug("获得锁");
                try {
//                    Thread.sleep(2000);
                    lock.wait(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();

        Sleeper.sleep(1);
        synchronized (lock) {
            log.debug("获得锁");
        }
    }
}

结果:

当调用sleep时的情况:

23:20:48.788 c.Test19 [t1] - 获得锁

当调用wait时的情况:

23:21:27.759 c.Test19 [t1] - 获得锁
23:21:28.768 c.Test19 [main] - 获得锁

解释:

上述结果说明sleep在暂停期间 不会释放锁 导致 这期间其他线程不能运行,而wait则可以释放锁