暮光博客

小心你讨厌的东西,因为你很可能被它塑造成某种形状

多线程中的「lost wake up 问题」

技术 7 条评论

问:Java 多线程中 wait() 方法为什么要放在同步块中?
答:为了避免「lost wake up 问题」,即「无法唤醒问题」。

什么是「lost wake up 问题」

我对「lost wake up 问题」的通俗理解:线程 A 调用 wait() 方法进入阻塞状态,接下来没有其他线程去唤醒线程 A,或者其他线程唤醒时机不对(早于线程 A 的 wait() ),导致线程 A 永远阻塞下去。

有中文资料对这个问题作出过解释:Java中wait()方法为什么要放在同步块中?(lost wake-up 问题),文中举了生产者和消费者的例子,但我觉得此文的结论并未触达核心,需要对文中的例子和结论多做一下补充,理解起来会方便一点。

现在有一个生产者线程和消费者线程:
先定义一个 obj 对象,并将其 count 属性的初始值设置为 0:

Object obj = new Object();
obj.count = 0;

生产者伪代码:

obj.count++;
obj.notify();

消费者伪代码:

while(obj.count<=0)
    obj.wait();
obj.count--;

两个线程启动,消费者检查 obj.count 的值,发现 obj.count <= 0 条件成立,但这时由于 CPU 的调度,发生上下文切换,生产者开始工作,执行了 count+1obj.notify(),也就是发出通知,准备唤醒一个阻塞的线程。然后 CPU 调度到消费者,此时消费者开始执行 obj.wait(),线程进入阻塞。但生产者已经早在消费者阻塞前执行了唤醒动作,也就导致消费者永远无法醒来了。

1644918-20190619224558253-730645351.png

随便加个锁能解决「lost wake up 问题」吗

不能,举个例子。

定义一把锁:

Lock lock1 = new Lock();

生产者伪代码:

lock1.lock();
obj.count++;
obj.notify();
lock1.unlock();

消费者伪代码:

lock1.lock();
while(count<=0)
    obj.wait();
obj.count--;
lock1.unlock();

两个线程启动,obj.count 初始值为 0。假设消费者先竞争到锁,while 中的 obj.count<=0 条件满足,执行 obj.wait() 使线程进入阻塞状态,lock1 锁没有被释放,所以生产者拿不到锁,也就无法 obj.notify() 通知消费者醒来,消费者将永远阻塞下去。

Java 中什么锁才能解决「lost wake up 问题」

只有上述例子中的 obj 对象锁才能避免这个问题,也就是将 obj.wait()obj.notify() 放进 obj 对象锁的同步块中。如果锁的不是例子中的 obj 对象,Java 就会抛出 IllegalMonitorStateException 异常。

生产者伪代码:

synchronized (obj) {
    obj.count++;
    obj.notify();
}

消费者伪代码:

synchronized (obj) {
    while(count<=0)
       obj.wait();
    obj.count--;
}

Java 中对 wait() 方法的注释中提到:线程在调用 obj.wait() 前必须要拿到当前 obj 对象的监视器 monitor 对象,即 obj 的锁。只有这样,当执行到 obj.wait() 时,该线程才可以暂时让出 obj 的同步锁并停止对锁的竞争,让其他正在等待此锁的线程可以得到同步锁并运行。

在上述例子中,消费者执行到 obj.wait() 时,让出了 obj 锁,停止了对锁的竞争,进入阻塞状态,紧接着生产者竞争到 obj 锁,执行了 obj.notify() 方法,唤醒了消费者,使消费者线程从阻塞状态重新回到就绪状态。

这里要注意的是,obj.notify() 并不是让生产者马上释放锁,也不是让消费者马上得到锁,而是通知消费者线程可以重新去参与锁的竞争了。

20年来,他们的过往故事都藏在这里
评论区 / 取消回复
选择表情选择表情
  1. JV

    大袋鼠? icon_mrgreen.gif

    回复
    1. @JV

      嗬,好久不见啊~

      回复
  2. 安中古天乐

    lock1.lock();
    while(count<=0)
    obj.wait();
    obj.count--;
    lock1.unlock();
    上述方式有问题,正确的写法应该是:
    Condition condition = lock.newCondition();
    lock.lock();
    try{
    while(count<=0){
    condition.awit();
    }
    count--;
    }finally{
    lock.unlock();
    }
    该种方式下,即使消费者先获取到锁,也是会最终释放掉锁的,而不是文中所说的
    只有obj 对象锁才能避免Lost Wake up 问题。

    回复
    1. 呵呵
      @安中古天乐

      文中写的 lock.lock 并非api的这个lock

      文中表达的想法我理解是 sync关键字 里面的wait 与notify 与sync(object) 对应的锁对象都为同一个

      比如 sync(obj) obj.wait 会释放sync获取的锁,

      回复
  3. java一个高薪的编程程序。

    回复
  4. 评论区样式不错, icon_wink.gif

    回复
  5. 玩的是嘛

    沙发,我就爱看这种 icon_wink.gif

    回复