【问题标题】:Why could this code fail?为什么这段代码会失败?
【发布时间】:2013-04-27 02:17:13
【问题描述】:

在查看this question 时,我注意到了这段代码:

class MyThread extends Thread {
  private boolean stop = false;

  public void run() {
    while(!stop) {
      doSomeWork();
    }
  }

  public void setStop() {
    this.stop = true;
  }
}

但是我不明白为什么会失败。其他线程是否无法访问“实际”停止变量?

【问题讨论】:

  • 你怎么称呼setStop()?来自同一 MyThread 实例还是不同?
  • 我不知道,不幸的是,这是我链接到的问题中的一个抽象示例
  • setStop 将从不同的线程中被调用。停止的所有 run 方法只需返回,它不需要标志。

标签: java multithreading java-threads


【解决方案1】:

JIT 编译器可以重新排序应用程序中的读取和写入,只要

  1. 动作顺序一致且
  2. 更改的操作不违反线程内语义。

这只是一种奇特的说法,所有动作都应该以与仅由单个线程执行的方式相同的方式发生。因此,您可以让 JIT 重新编译您的代码,使其看起来像这样

class MyThread extends Thread {
  private boolean stop = false;

  public void run() {
    if(!stop){
       while(true){

       }
    }
  }

这是一种称为提升的合法优化。它的行为仍然与串行相同,但在使用多个线程时会提供令人惊讶的结果。

通过声明一个 volatile 字段,您是在告诉 Java 不要执行任何重新排序。以及 Nathan Hughes 提到的内存一致性

【讨论】:

  • JVM 会在提升之前检查doSomeWork() 是否设置了stop 字段吗?我认为是的,否则优化似乎不是那么理想:)
  • @dlev 是的,绝对是!否则会导致可怕的优化。
【解决方案2】:

实例变量stop 需要是可变的,否则不能保证其他线程会看到它的变化。工作中有很多相互冲突的利益:线程想要程序状态的一致视图,CPU 想要能够缓存数据,JVM 想要能够重新排序指令。使实例变量 volatile 意味着它不能被缓存,并且在建立限制指令重新排序的关系之前发生。

请参阅this other answer (+1),了解在不将变量标记为 volatile 的情况下可能发生的重新排序的一个很好的示例。

(顺便说一句,using interruption for thread cancellation 比使用实例变量更可取。)

【讨论】:

  • 你应该提到提升,这是失败的第一大原因。
  • @user2246674 我为地址提升创建了自己的答案。
  • @John:这与指令重新排序有什么不同吗?
  • @NathanHughes 你说得对。我想您是否包含特定类型的重新排序并不明显。尽管您确实提到了,但您的推理似乎更多地集中在内存可见性而不是重新排序上。
【解决方案3】:

变量 stop 必须声明为 volatile。

虽然我更喜欢使用中断来停止线程。

【讨论】:

    【解决方案4】:

    不保证其他线程会看到更新的停止值 - 您需要建立“发生在之前”的关系。最简单的方法是让 stop volatile。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多