我有两个不同步的线程在一个紧密循环中,将全局变量 X 倍递增 (x=100000(。
全局的正确最终值应该是 2*X,但由于它们是不同步的,它会更少,根据经验,它通常只比 X 多一点
但是,在所有测试运行中,全局的值从未低于 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);
}
}
想象一下以下场景:
- 线程 A 从
global
读取0
的初始值 - 线程 B 对
global
执行 99999 更新 - 线程 A 将
1
写入global
- 线程 B 从
global
读取1
- 线程 A 在
global
上执行其剩余的 99999 次更新 - 线程 B 将
2
写入global
然后,两个线程都完成了,但结果值是2
,而不是2 * 100000
,也不是100000
。
请注意,上面的示例只是使用了错误的计时,而不会让任何线程感知到无序读取或写入另一个线程(在没有同步的情况下是允许的(,也不会丢失更新(这里也允许(。
换句话说,当global
变量被声明为volatile
时,上面显示的情况甚至是可能的。
对读取和写入及其可见性进行推理是一个常见的错误,但隐式地假设执行线程代码的特定时间。但不能保证这些线程以类似的指令计时并行运行。
但这在测试方案中仍可能发生,因此它们不会揭示其他可能的行为。此外,某些法律行为可能永远不会发生在特定的硬件或特定的 JVM 实现上,而开发人员仍然必须考虑这一点。优化器将递增循环替换为等效的global += 100000
,这可能很好,在此类测试中很少显示中间值,但是在循环主体中插入其他一些重要的操作可能会完全改变行为。