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