出现虚假唤醒的地方java.lang.Object#wait()方法和它的重载方法
java.util.concurrent.locks.Condition#await()方法和它的重载方法
java.util.concurrent.locks.Condition#awaitUntil方法与它的重载方法
解决办法
1. 在java.lang.Object#wait()方法上面有这样一段注释:
Causesthe current thread to waituntilanother thread invokes the
*{@linkjava.lang.Object#notify()} method or the
*{@linkjava.lang.Object#notifyAll()} method for this object.
*Inother words,thismethod behaves exactlyasifit simply
*performs the call{@codewait(0)}.
*
*Thecurrent thread must ownthisobject's monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object's monitor to wake up
*either through a call to the{@codenotify}methodorthe
*{@codenotifyAll}method.Thethreadthenwaitsuntilit can
*re-obtain ownership of the monitorandresumes execution.
*
Asinthe one argument version,interruptsandspurious wakeups are
*possible,andthismethod should always be usedina loop:
*
*synchronized(obj){
*while(<condition doesnothold>)
*obj.wait();
*...// Perform action appropriate to condition
*}
*
*Thismethod should only be calledbya thread thatisthe owner
*ofthisobject's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
可以看到,这里提供的方法是使用while循环,重新检测条件或者timeout。
2. 其他解法
1.等待直到条件变成true
不正确的代码示范:
Thread 1:synchronized(this){
if(!condition){
wait();
}
doStuffAssumingConditionIsTrue();
}
Thread 2:
synchronized(this){
condition=true;
notify();
}
如果wait()方法被虚假唤醒,然后doStuffAssumingConditionIsTrue()会被执行,尽管此时condition的值是false。如果用while来代替while
Thread 1:synchronized(this){
while(!condition){
wait();
}
doStuffAssumingConditionIsTrue();
}
这就保证了只有在condition为true时doStuffAssumingConditionIsTrue()才会被执行。需要注意的一点是,condition变量必须在同步代码块内部;否则的话,你将会在对condition变量判断和设值时存在一个竞态条件。
2. 等待直到一个事件发生
不正确的代码示范:
Thread 1:
synchronized(this){
wait();
doStuffAfterEvent();
}
Thread 2:// when event occurs
synchronized(this){
notify();
}
如果wait()方法被虚假唤醒,doStuffAfterEvent() 会在事件还没有发生时就执行。可以参考上面例子中的while循环方式来重写这个方法。
3. 等待直到条件变成true或者超时时
不正确的代码示范:
synchronized(this){
if(!condition){
wait(timeout);
}
doStuffAssumingConditionIsTrueOrTimeoutHasOccurred();
}
虚假唤醒会导致doStuffAssumingConditionIsTrueOrTimeoutHasOccurred()方法在条件仍然为false且还没有超时时被执行。
应该这样写:synchronized(this){
longnow=System.currentTimeMillis();
longdeadline=now+timeout;
while(!condition&&now
wait(deadline-now);
now=System.currentTimeMillis();
}
doStuffAssumingConditionIsTrueOrTimeoutHasOccurred();
}
4.等待固定长的时间
第一种,警告:这种类型的waiting/sleeping常常用于本意就是等待所有的操作完成,然后执行的场景。如果你是这种场景,最好考虑用上面示例中的方式重写你的代码。否则你必须依赖系统时间,系统时间在不同机器上是不一样的。
错误的代码示范:
synchronized(this){
// Give some time for the foos to bar
wait(1000);
}
虚假唤醒不会等待完整的1000 ms. Thread.sleep(),不会被虚假唤醒,所以你应该使用Thread.sleep()来代替。Thread.sleep(1000);
5. 一直等待
错误代码示范:
synchronized(this){
// wait forever
wait();
}
虚假唤醒会导致它不会永久等待,需要把wait() 包裹在 while (true) 循环中:synchronized(this){
// wait forever
while(true){
wait();
}
}
6. 比较乐观的例子(认为不会虚假唤醒)
WaitNotInLoopPositiveCases.java:
/*
* Copyright 2013 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
packagecom.google.errorprone.bugpatterns.testdata;
importjava.util.Date;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.locks.Condition;
/** @author eaftan@google.com (Eddie Aftandilian) */
publicclassWaitNotInLoopPositiveCases{
booleanflag=false;
publicvoidtestIfInsteadOfLoop(){
synchronized(this){
if(!flag){
try{
// BUG: Diagnostic contains: wait() must always be called in a loop
// Did you mean 'while (!flag) {'?
wait();
}catch(InterruptedExceptione){
}
}
}
}
publicvoidtestWaitLong()throwsInterruptedException{
// BUG: Diagnostic contains: wait(long) must always be called in a loop
wait(1000);
}
publicvoidtestWaitLongInt()throwsException{
// BUG: Diagnostic contains: wait(long,int) must always be called in a loop
wait(1000,1000);
}
publicvoidtestAwait(Conditioncond)throwsException{
// BUG: Diagnostic contains: await() must always be called in a loop
cond.await();
}
publicvoidtestAwaitWithFix(Conditioncond)throwsException{
synchronized(this){
if(!flag){
try{
// BUG: Diagnostic contains: await() must always be called in a loop
// Did you mean 'while (!flag) {'?
cond.await();
}catch(InterruptedExceptione){
}
}
}
}
publicvoidtestAwaitLongTimeUnit(Conditioncond)throwsException{
// BUG: Diagnostic contains:
// await(long,java.util.concurrent.TimeUnit) must always be called in a loop
cond.await(1,TimeUnit.SECONDS);
}
publicvoidtestAwaitNanos(Conditioncond)throwsException{
// BUG: Diagnostic contains: awaitNanos(long) must always be called in a loop
cond.awaitNanos(1000000);
}
publicvoidtestAwaitUninterruptibly(Conditioncond)throwsException{
// BUG: Diagnostic contains: awaitUninterruptibly() must always be called in a loop
cond.awaitUninterruptibly();
}
publicvoidtestAwaitUntil(Conditioncond)throwsException{
// BUG: Diagnostic contains: awaitUntil(java.util.Date) must always be called in a loop
cond.awaitUntil(newDate());
}
}
7. 悲观的例子(认为会虚假唤醒)
WaitNotInLoopNegativeCases.java:/*
* Copyright 2013 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
packagecom.google.errorprone.bugpatterns.testdata;
/**
* @author eaftan@google.com (Eddie Aftandilian)
*
TODO(eaftan): Add test cases for enhanced for loop, loop outside synchronized block.
*/
publicclassWaitNotInLoopNegativeCases{
booleanflag=true;
publicvoidtest1(){
synchronized(this){
while(!flag){
try{
wait();
}catch(InterruptedExceptione){
}
}
}
}
publicvoidtest2(){
synchronized(this){
while(!flag){
try{
wait(1000);
}catch(InterruptedExceptione){
}
}
}
}
publicvoidtest3(){
synchronized(this){
while(!flag){
try{
wait(1000,1000);
}catch(InterruptedExceptione){
}
}
}
}
// This code is incorrect, but this check should not flag it.
publicvoidtestLoopNotInSynchronized(){
while(!flag){
synchronized(this){
try{
wait();
}catch(InterruptedExceptione){
}
}
}
}
publicvoidtestDoLoop(){
synchronized(this){
do{
try{
wait();
}catch(InterruptedExceptione){
}
}while(!flag);
}
}
publicvoidtestForLoop(){
synchronized(this){
for(;!flag;){
try{
wait();
}catch(InterruptedExceptione){
}
}
}
}
publicvoidtestEnhancedForLoop(){
int[]arr=newint[100];
synchronized(this){
for(inti:arr){
try{
wait();
}catch(InterruptedExceptione){
}
}
}
}
privatevoidwait(Objectobj){}
publicvoidtestNotObjectWait(){
wait(newObject());
}
}
上面的内容译自:http://errorprone.info/bugpattern/WaitNotInLoop
3. 参考:
http://errorprone.info/bugpattern/WaitNotInLoop