Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object. * In other words, this method behaves exactly as if it simply * performs the call {@code wait(0)}. * <p> * The current thread must own this object'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 {@code notify} method or the * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. * <p>As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(); * ... // Perform action appropriate to condition * } * </pre> * This method should only be called by a thread that is the owner * of this object'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。
不正确的代码示范:
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变量判断和设值时存在一个竞态条件。
不正确的代码示范:
Thread 1:
synchronized (this) { wait(); doStuffAfterEvent();}
Thread 2:
// when event occurssynchronized (this) { notify();}
如果wait()方法被虚假唤醒,doStuffAfterEvent() 会在事件还没有发生时就执行。可以参考上面例子中的while循环方式来重写这个方法。
不正确的代码示范:
synchronized (this) { if (!condition) { wait(timeout); } doStuffAssumingConditionIsTrueOrTimeoutHasOccurred();}
虚假唤醒会导致doStuffAssumingConditionIsTrueOrTimeoutHasOccurred()方法在条件仍然为false且还没有超时时被执行。
应该这样写:
synchronized (this) { long now = System.currentTimeMillis(); long deadline = now + timeout; while (!condition && now < deadline) { wait(deadline - now); now = System.currentTimeMillis(); } doStuffAssumingConditionIsTrueOrTimeoutHasOccurred();}
第一种,警告:这种类型的waiting/sleeping常常用于本意就是等待所有的操作完成,然后执行的场景。如果你是这种场景,最好考虑用上面示例中的方式重写你的代码。否则你必须依赖系统时间,系统时间在不同机器上是不一样的。
错误的代码示范:
synchronized (this) { // Give some time for the foos to bar wait(1000);}
虚假唤醒不会等待完整的1000 ms. Thread.sleep(),不会被虚假唤醒,所以你应该使用Thread.sleep()来代替。
Thread.sleep(1000);
错误代码示范:
synchronized (this) { // wait forever wait();}
虚假唤醒会导致它不会永久等待,需要把wait() 包裹在 while (true) 循环中:
synchronized (this) { // wait forever while (true) { wait(); }}
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. */
package com.google.errorprone.bugpatterns.testdata;
import java.util.Date;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Condition;
/** @author eaftan@google.com (Eddie Aftandilian) */public class WaitNotInLoopPositiveCases {
boolean flag = false;
public void testIfInsteadOfLoop() { synchronized (this) { if (!flag) { try { // BUG: Diagnostic contains: wait() must always be called in a loop // Did you mean 'while (!flag) {'? wait(); } catch (InterruptedException e) { } } } }
public void testWaitLong() throws InterruptedException { // BUG: Diagnostic contains: wait(long) must always be called in a loop wait(1000); }
public void testWaitLongInt() throws Exception { // BUG: Diagnostic contains: wait(long,int) must always be called in a loop wait(1000, 1000); }
public void testAwait(Condition cond) throws Exception { // BUG: Diagnostic contains: await() must always be called in a loop cond.await(); }
public void testAwaitWithFix(Condition cond) throws Exception { synchronized (this) { if (!flag) { try { // BUG: Diagnostic contains: await() must always be called in a loop // Did you mean 'while (!flag) {'? cond.await(); } catch (InterruptedException e) { } } } }
public void testAwaitLongTimeUnit(Condition cond) throws Exception { // BUG: Diagnostic contains: // await(long,java.util.concurrent.TimeUnit) must always be called in a loop cond.await(1, TimeUnit.SECONDS); }
public void testAwaitNanos(Condition cond) throws Exception { // BUG: Diagnostic contains: awaitNanos(long) must always be called in a loop cond.awaitNanos(1000000); }
public void testAwaitUninterruptibly(Condition cond) throws Exception { // BUG: Diagnostic contains: awaitUninterruptibly() must always be called in a loop cond.awaitUninterruptibly(); }
public void testAwaitUntil(Condition cond) throws Exception { // BUG: Diagnostic contains: awaitUntil(java.util.Date) must always be called in a loop cond.awaitUntil(new Date()); }}
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. */
package com.google.errorprone.bugpatterns.testdata;
/** * @author eaftan@google.com (Eddie Aftandilian) * <p>TODO(eaftan): Add test cases for enhanced for loop, loop outside synchronized block. */public class WaitNotInLoopNegativeCases {
boolean flag = true;
public void test1() { synchronized (this) { while (!flag) { try { wait(); } catch (InterruptedException e) { } } } }
public void test2() { synchronized (this) { while (!flag) { try { wait(1000); } catch (InterruptedException e) { } } } }
public void test3() { synchronized (this) { while (!flag) { try { wait(1000, 1000); } catch (InterruptedException e) { } } } }
// This code is incorrect, but this check should not flag it. public void testLoopNotInSynchronized() { while (!flag) { synchronized (this) { try { wait(); } catch (InterruptedException e) { } } } }
public void testDoLoop() { synchronized (this) { do { try { wait(); } catch (InterruptedException e) { } } while (!flag); } }
public void testForLoop() { synchronized (this) { for (; !flag; ) { try { wait(); } catch (InterruptedException e) { } } } }
public void testEnhancedForLoop() { int[] arr = new int[100]; synchronized (this) { for (int i : arr) { try { wait(); } catch (InterruptedException e) { } } } }
private void wait(Object obj) {}
public void testNotObjectWait() { wait(new Object()); }}
上面的内容译自:http://errorprone.info/bugpattern/WaitNotInLoop
http://errorprone.info/bugpattern/WaitNotInLoop