前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实战之java中线程的虚假唤醒

实战之java中线程的虚假唤醒

作者头像
山行AI
发布2019-06-28 16:39:20
1.3K0
发布2019-06-28 16:39:20
举报
文章被收录于专栏:山行AI山行AI山行AI

出现虚假唤醒的地方

  • java.lang.Object#wait()方法和它的重载方法
  • java.util.concurrent.locks.Condition#await()方法和它的重载方法
  • java.util.concurrent.locks.Condition#awaitUntil方法与它的重载方法

解决办法

1. 在java.lang.Object#wait()方法上面有这样一段注释:

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 (&lt;condition does not hold&gt;)     *             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。

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 occurssynchronized (this) {  notify();}

如果wait()方法被虚假唤醒,doStuffAfterEvent() 会在事件还没有发生时就执行。可以参考上面例子中的while循环方式来重写这个方法。

3. 等待直到条件变成true或者超时时

不正确的代码示范:

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();}

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. */
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());  }}

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. */
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

3. 参考:

http://errorprone.info/bugpattern/WaitNotInLoop

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 出现虚假唤醒的地方
  • 解决办法
    • 1. 在java.lang.Object#wait()方法上面有这样一段注释:
      • 2. 其他解法
        • 1.等待直到条件变成true
        • 2. 等待直到一个事件发生
        • 3. 等待直到条件变成true或者超时时
        • 4.等待固定长的时间
        • 5. 一直等待
        • 6. 比较乐观的例子(认为不会虚假唤醒)
        • 7. 悲观的例子(认为会虚假唤醒)
      • 3. 参考:
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档