3

我在一个紧密循环中有两个不同步的线程,将全局变量递增 X 次(x=100000)。

全局的正确最终值应该是 2*X,但是由于它们是不同步的,所以它会更小,根据经验,它通常只是略高于 X

但是,在所有测试运行中, global 的值从未低于 X 。

最终结果是否可能小于 x (小于 100000 )?

public class TestClass {
    static int global;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {  for(int i=0; i < 100000; ++i) {     TestClass.global++;  }  });
        Thread t2 = new Thread( () -> { for(int i=0; i < 100000; ++i) {     TestClass.global++;  }  });
        t.start(); t2.start();
        t.join(); t2.join();
        System.out.println("global = " + global);
    }
}
4

1 回答 1

3

想象一下以下场景:

  • 线程 A0global
  • 线程 B 执行 99999 次更新global
  • 线程 A1写入global
  • 线程 B1global
  • 线程 A 执行其剩余的 99999 次更新global
  • 线程 B2写入global

然后,两个线程都完成了,但结果值为2, not 2 * 100000, nor 100000

请注意,上面的示例只是使用了错误的时序,而没有让任何线程感知到另一个线程的无序读取或写入(在没有同步的情况下允许这样做),也没有丢失更新(这里也允许这样做)。

换句话说,当global变量被声明时,上面显示的场景甚至是可能的volatile

推理读取和写入及其可见性是一个常见的错误,但隐含地假设执行线程代码的特定时间。但不能保证这些线程以相似的指令时序并行运行。

但这可能仍然发生在您的测试场景中,因此它们没有揭示其他可能的行为。此外,某些合法行为可能永远不会在特定硬件或特定 JVM 实现上发生,而开发人员仍然必须对此负责。优化器将递增循环替换为在这样的测试中很少显示中间值的等价物可能很好,global += 100000但是在循环体中插入一些其他重要的操作可能会完全改变行为。

于 2019-12-16T17:45:18.067 回答