notify丢失、虚假唤醒

  notify丢失:

  假设线程A因为某种条件在条件队列中等待,同时线程B因为另外一种条件在同一个条件队列中等待,也就是说线程A/B都被同一个Object.wait()挂起,但是等待的条件不同。

  现在假设线程B的线程被满足,线程C执行一个notify操作,此时JVM从Object.wait()的多个线程(A/B)中随机挑选一个唤醒,不幸的是唤醒了A。此时A的条件不满足,于是A继续挂起。而此时B仍然在傻傻的等待被唤醒的信号。也就是说本来给B的通知却被一个无关的线程持有了,真正需要通知的线程B却没有得到通知,而B仍然在等待一个已经发生过的通知。

       如果使用notifyall,则能够避免此问题。notifyall会唤醒所有正在等待的线程,线程C发出的通知线程A同样能够收到,但是由于对于A没用,所以A继续挂起,而线程B也收到了此通知,于是线程B正常被唤醒。

  既然notifyall能够解决单一notify丢失通知的问题,那么为什么不总是使用notifyall替换notify呢?

  假设有N个线程在条件队列中等待,调用notifyall会唤醒所有线程,然后这N个线程竞争同一个锁,最多只有一个线程能够得到锁,于是其它线程又回到挂起状态。这意味每一次唤醒操作可能带来大量的上下文切换(如果N比较大的话),同时有大量的竞争锁的请求。这对于频繁的唤醒操作而言性能上可能是一种灾难。

  如果说总是只有一个线程被唤醒后能够拿到锁,那么为什么不使用notify呢?所以某些情况下使用notify的性能是要高于notifyall的。

  如果满足下面的条件,可以使用单一的notify取代notifyall操作:

  相同的等待者,也就是说等待条件变量的线程操作相同,每一个从wait放回后执行相同的逻辑,同时一个条件变量的通知至多只能唤醒一个线程。

  lock上可以存在多个等待队列,应该可以比较好的解决notify丢失的问题。

 

   wait最好放在while循环中,以避免“虚假唤醒”的情形。即线程由于某些特殊情况,不是被notify或者notifyAll所唤醒,所以还需要再次判断条件是否成立。

  所以wait应该放到while循环中,而不是简单的使用if条件来判断。

posted on 2015-08-23 23:33  lnlvinso  阅读(3109)  评论(0编辑  收藏