Skip to content

Wait/Notify

image.png

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

API

  • 需要当前线程获取对象锁,才能调用
  • notify 唤醒方法不可以指定唤醒
    • LockSupport.park();
    • LockSupport.unpark(Thread t);
  • obj.wait():让进入object监视器的线程到waitSet等待
    • 当调用无参wait会一直等待下去
    • 调用有参的wait则会等待一段时间就会执行
  • obj.notify():在object 上正在waitSet等待的线程中挑一个唤醒
  • obj.notifyAll():唤醒全部的waitSet线程
java
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
    new Thread(() -> {
        synchronized (lock) {
            try {
                lock.wait();
                log.info("run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "t1").start();
    new Thread(() -> {
        synchronized (lock) {
            try {
                lock.wait();
                log.info("run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "t2").start();
    Thread.sleep(2);
    synchronized (lock) {
        lock.notifyAll();
    }
}
  • sleep(long n) 和 wait(long n) 的区别
    • sleep是Thread方法,而wait是Object的方法
    • sleep不需要强制和synchronized配合使用,但wait需要和synchronized-起用
    • sleep在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁。
    • 它们状态TIMED_ WAITING

park & unpark

java
// 暂停当前线程
LockSupport.park();

// 启动某个线程
LockSupport.unpark(Thread t);

特点

与Object的wait & notify相比

  • wait, notify 和notifyAll必须配合Object Monitor一起使用,而unpark不必
  • park & unpark是以线程为单位来[阻塞]和[唤醒]线程,而notify只能随机唤醒一个等待线程
  • notifyAll是唤醒所有等待线程,就不那么[精确]
  • park & unpark可以先unpark,而wait & notify不能先notify

原理

每个线程都有自己的一个Parker对象,由三部分组成_ counter, cond 和 mutex 打个比喻

  • 线程就像一个旅人, Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包

中的备用干粮(0 为耗尽,1 为充足)

  • 调用park就是要看需不需要停下来歇息
    • 如果备用干粮耗尽,那么钻进帐篷歇息
    • 如果备用干粮充足,那么不需停留,继续前进
  • 调用unpark,就好比令干粮充足
    • 如果这时线程还在帐篷,就唤醒让他继续前进
    • 如果这时线程还在运行,那么下次他调用park时,仅是消耗掉备用干粮,不需停留继续前进
      • 因为背包空间有限,多次调用unpark仅会补充一份备用干粮

  1. 当前线程调用Unsafe.park(方法
  2. 检查_ counter ,本情况为0,这时,获得_ mutex 互斥锁
  3. 线程进入_ cond 条件变量阻塞
  4. 设置_ counter= 0