线程虚假唤醒问题


当线程从等待已发出信号的条件变量中醒来,却发现它等待的条件未得到满足时,就会发生虚假唤醒。之所以称为虚假,是因为该线程似乎无缘无故地被唤醒了。但是虚假唤醒不会无缘无故发生:它们通常是因为在发出条件变量信号和等待线程最终运行之间,另一个线程运行并更改了条件。线程之间存在竞争条件,典型的结果是有时,在条件变量上唤醒的线程首先运行,赢得竞争,有时它运行第二,失去竞争。

方法定义

wait


使当前线程等待,直到另一个线程调用该 notify()方法或 notifyAll()此对象的方法,或者经过了指定的时间量。
当前线程必须拥有该对象的监视器。


线程也可以在没有被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防范它,如果条件不满足则继续等待。换句话说,等待应该总是在循环中发生,就像这样:

 同步(对象){
     while (<条件不成立>)
         obj.wait(超时);
     ... // 执行适合条件的操作
 }

notify


唤醒正在此对象的监视器上等待的单个线程。如果有任何线程正在等待该对象,则选择其中一个被唤醒。该选择是任意的,并由实施自行决定。

notifyAll


唤醒正在此对象的监视器上等待的所有线程。线程通过调用其中一种 wait方法在对象的监视器上等待。

原因分析

1、创建资源类Share
属性:int count = 1
方法:incr 加1 | decr 减1

2、为incr方法上锁,判断count != 0,等待,否则 加1,并唤醒其他线程

3、为decr方法上锁,判断count != 1,等待,否则 减1,并唤醒其他线程

4、在main方法中创建两个线程分别调用incr和decr实现加减1操作,正常交替执行,打印结果为1或者0

5、在main方法中创建四个线程分别调用incr和decr实现加减1操作,打印结果并非为0和1

代码示例

/***
 * 创建资源类Share
 * 
 * 属性:int count = 1
 * 方法:incr 加1 | decr 减1
 */
class Share {
  
  Lock lock = new ReentrantLock();
  Condition condition = lock.newCondition();
  private int number = 1;

  /***
   * 为incr方法上锁,判断count != 0,等待,否则 加1,并唤醒其他线程
   * @throws InterruptedException
   */
  public void incr() throws InterruptedException {
    lock.lock();
    try {
      if (number != 0) {
        condition.await();
      }
      number++;
      System.out.println(Thread.currentThread().getName() + " number: " + number);
      condition.signalAll();
    } finally {
      lock.unlock();
    }
  }


  /***
   * 为decr方法上锁,判断count != 1,等待,否则 减1,并唤醒其他线程
   * @throws InterruptedException
   */
  public void decr() throws InterruptedException {
    lock.lock();
    try {
      if (number != 1) {
        condition.await();
      }
      number--;
      System.out.println(Thread.currentThread().getName() + " number: " + number);
      condition.signalAll();
    } finally {
      lock.unlock();
    }
  }
}
    decr number: 0
    decr number: 1
    incr number: 2 // 出现2
    decr number: 1
    decr number: 0
    decr number: 1
    decr number: 0
    incr number: 1
    decr number: 0
    decr number: 1

解决方案


主要原因就是线程唤醒之后,会在被唤醒的地方继续执行,如果使用if判断,则不会再次执行if判断,而直接执行if代码块外操作。


解决的方案就是使用while做判断,直到条件满足才会向下执行while代码块外操作。


在if中使用wait方法非常的危险!!!

Last modification:August 25, 2022
如果觉得我的文章对你有用,请随意赞赏