Mat*_*ulk 8 java race-condition
当我运行以下代码时,最终输出始终为正,如果我切换“x++”和“x--”的顺序,则最终输出始终为负。这个竞争条件的哪些部分被跳过是否有某种顺序?非常感谢任何帮助理解!
public class DataRace {
private static class MyThreadCode implements Runnable {
private static int x = 0; // NOTE THAT X IS STATIC!!!
@Override
public void run() {
for (int i = 0; i < 10000000; i++) {
x++;
x--;
}
System.out.println(x + " " + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread t = new Thread(new MyThreadCode());
t.start();
}
}
}
Run Code Online (Sandbox Code Playgroud)
我们这里有两个问题:
x++和x--不是原子的(请参阅为什么 i++ 不是原子的?),您会遇到竞争条件。线程 1 将加载x(0) 的值。然后,CPU 可以切换到线程 2,该线程也加载当前线程x(0)。现在,两者都在本地增加值,稍后,它们将设置x。这意味着我们失去了一个增量。x没有标记volatile,我们也没有使用synchronized关键字,所以您无法保证线程确实看到 的实际值x。可能该值已被另一个线程更新,但由于 Java 可见性保证,您无法保证其他线程看到的内容(更新值或某些过时的“缓存”值)。也可能是其他线程尚未将更新x值写回内存(仅写入L1/L2 Cache)。我对代码进行了一些操作,如果我将for循环减少到1000迭代,我会得到相同代码的负值和正值。这可能暗示如果我们有多次迭代,JVM 会优化代码。如果我们只有 1000 次迭代(我猜这并不算多),JVM 可能会决定按照编写的方式运行代码。
我怀疑这两个问题对结果都有一定的影响。例如,如果您加载x,处理器可能会将该值加载到 L1 缓存中,如果您随后执行第二个操作,它可能会直接从 L1 缓存而不是主内存加载该值。这可能意味着第二次操作不会导致/导致“丢失更新”较少。
但要真正找出发生这种情况的原因,我认为您需要深入研究 Java 规范,甚至 CPU 如何处理此类情况。