为什么这个竞赛条件的输出有些一致



当我运行以下代码时,最终输出总是正的,如果我切换"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();
}
}
}

这里有两个问题:

  • 因为x++x--不是原子的(请参阅为什么i++不是原子的?(,所以您得到了一个竞赛条件。线程1将加载x(0(的值。然后CPU可以切换到线程2,线程2也加载当前的x(0(。现在两者都在本地增加值,稍后将设置x。这意味着我们失去了增量
  • 因为x没有标记为volatile,我们也没有使用synchronized关键字,所以您不能保证线程确实看到了x的实际值。可能是,该值已经被另一个线程更新,但由于Java可见性保证,您无法保证其他线程看到什么(更新值或一些过时的"缓存"值(。也可能是其他线程尚未将更新x值写回内存(仅写入L1/L2缓存(

我对代码进行了一些处理,如果我将for循环减少到1000迭代,我会得到同一代码的负值和正值。如果我们有多次迭代,这可能是JVM优化代码的一个提示。如果我们只有1000次迭代,我想这并不是那么多,JVM可能会决定在编写代码时运行代码。

我怀疑这两个问题对结果都有一定的影响。例如,如果加载x,处理器可能会将该值加载到一级缓存中,然后如果执行第二个操作,它可能会直接从一级缓存而不是主内存加载该值。这可能意味着第二次操作导致"无/少";丢失的更新";。

但要想真正弄清楚为什么会发生这种情况,我想你应该深入研究Java规范,甚至CPU是如何处理这种情况的。

我怀疑这是因为操作系统进程调度程序的工作方式。

由于x上的操作是不同步的,所以当操作系统启动特定的线程操作时,线程将看到x的值。由于第一个操作是递增的,在100个线程时,甚至在准备开始递减之前,x的值可能已经是100或比上一个递增更大的数字。

您可以在运行方法中添加一些调试语句来查看此处发生的情况。我把跑步循环次数减少到2,这是我得到的。这些语句没有按顺序排列,但每个线程的第二个log语句显示了偏差。

@Override
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 2; i++) {
x++;
System.out.println("Value of x by "+ Thread.currentThread().getName() + "is "+x);
x--;
}
}
Value of x by Thread-53is 54
Value of x by Thread-53is 100
Value of x by Thread-81is 82
Value of x by Thread-81is 99
Value of x by Thread-57is 58
Value of x by Thread-39is 40
Value of x by Thread-39is 98
Value of x by Thread-28is 29
Value of x by Thread-28is 97
Value of x by Thread-60is 61
Value of x by Thread-94is 95
Value of x by Thread-94is 96
Value of x by Thread-99is 100
Value of x by Thread-99is 95
Value of x by Thread-78is 79
Value of x by Thread-78is 94
Value of x by Thread-87is 88
Value of x by Thread-87is 93
Value of x by Thread-84is 85
Value of x by Thread-84is 92
Value of x by Thread-41is 42
Value of x by Thread-41is 91
Value of x by Thread-74is 75
Value of x by Thread-74is 90
Value of x by Thread-92is 93
Value of x by Thread-92is 89
Value of x by Thread-12is 13
Value of x by Thread-12is 88
Value of x by Thread-98is 99
Value of x by Thread-98is 87
Value of x by Thread-8is 9
Value of x by Thread-8is 86
Value of x by Thread-66is 67
Value of x by Thread-88is 89
Value of x by Thread-49is 50
Value of x by Thread-49is 85
Value of x by Thread-47is 48
Value of x by Thread-14is 15
Value of x by Thread-14is 84
Value of x by Thread-70is 71
Value of x by Thread-61is 62
Value of x by Thread-70is 83
Value of x by Thread-91is 92
Value of x by Thread-96is 98
Value of x by Thread-96is 82
Value of x by Thread-67is 68
Value of x by Thread-27is 28
Value of x by Thread-27is 81
Value of x by Thread-0is 1
Value of x by Thread-0is 80
Value of x by Thread-75is 76
Value of x by Thread-75is 79
Value of x by Thread-3is 4
Value of x by Thread-3is 78
Value of x by Thread-56is 57
Value of x by Thread-56is 77
Value of x by Thread-97is 97
Value of x by Thread-22is 23
Value of x by Thread-22is 76
Value of x by Thread-15is 16
Value of x by Thread-15is 75
Value of x by Thread-64is 65
Value of x by Thread-63is 64
Value of x by Thread-59is 60
Value of x by Thread-33is 34
Value of x by Thread-9is 10
Value of x by Thread-45is 46
Value of x by Thread-37is 38
Value of x by Thread-10is 11
Value of x by Thread-76is 77
Value of x by Thread-77is 78
Value of x by Thread-93is 94
Value of x by Thread-6is 7
Value of x by Thread-52is 53
Value of x by Thread-26is 27
Value of x by Thread-86is 87
Value of x by Thread-30is 31
Value of x by Thread-82is 83
Value of x by Thread-83is 84
Value of x by Thread-89is 90
Value of x by Thread-73is 74
Value of x by Thread-32is 33
Value of x by Thread-79is 80
Value of x by Thread-4is 5
Value of x by Thread-79is 74
Value of x by Thread-32is 74
Value of x by Thread-73is 74
Value of x by Thread-89is 74
Value of x by Thread-83is 74
Value of x by Thread-82is 74
Value of x by Thread-30is 74
Value of x by Thread-86is 74
Value of x by Thread-26is 74
Value of x by Thread-52is 74
Value of x by Thread-6is 74
Value of x by Thread-93is 74
Value of x by Thread-77is 74
Value of x by Thread-76is 74
Value of x by Thread-10is 74
Value of x by Thread-37is 74
Value of x by Thread-45is 74
Value of x by Thread-9is 74
Value of x by Thread-33is 74
Value of x by Thread-59is 74
Value of x by Thread-63is 74
Value of x by Thread-64is 74
Value of x by Thread-97is 76
Value of x by Thread-35is 36
Value of x by Thread-24is 25
Value of x by Thread-40is 41
Value of x by Thread-67is 81
Value of x by Thread-46is 47
Value of x by Thread-58is 59
Value of x by Thread-91is 82
Value of x by Thread-36is 37
Value of x by Thread-11is 12
Value of x by Thread-25is 26
Value of x by Thread-61is 83
Value of x by Thread-31is 32
Value of x by Thread-42is 43
Value of x by Thread-34is 35
Value of x by Thread-85is 86
Value of x by Thread-47is 84
Value of x by Thread-5is 6
Value of x by Thread-29is 30
Value of x by Thread-88is 85
Value of x by Thread-66is 85
Value of x by Thread-90is 91
Value of x by Thread-21is 22
Value of x by Thread-17is 18
Value of x by Thread-7is 8
Value of x by Thread-54is 55
Value of x by Thread-62is 63
Value of x by Thread-16is 17
Value of x by Thread-80is 81
Value of x by Thread-38is 39
Value of x by Thread-2is 3
Value of x by Thread-1is 2
Value of x by Thread-13is 14
Value of x by Thread-72is 73
Value of x by Thread-20is 21
Value of x by Thread-23is 24
Value of x by Thread-51is 52
Value of x by Thread-55is 56
Value of x by Thread-95is 96
Value of x by Thread-65is 66
Value of x by Thread-50is 51
Value of x by Thread-19is 20
Value of x by Thread-60is 96
Value of x by Thread-71is 72
Value of x by Thread-68is 69
Value of x by Thread-44is 45
Value of x by Thread-57is 98
Value of x by Thread-48is 49
Value of x by Thread-48is 43
Value of x by Thread-18is 19
Value of x by Thread-69is 70
Value of x by Thread-43is 44
Value of x by Thread-69is 42
Value of x by Thread-18is 42
Value of x by Thread-44is 44
Value of x by Thread-68is 44
Value of x by Thread-71is 44
Value of x by Thread-19is 45
Value of x by Thread-50is 45
Value of x by Thread-65is 45
Value of x by Thread-95is 45
Value of x by Thread-55is 45
Value of x by Thread-51is 45
Value of x by Thread-23is 45
Value of x by Thread-20is 45
Value of x by Thread-72is 45
Value of x by Thread-13is 45
Value of x by Thread-1is 45
Value of x by Thread-2is 45
Value of x by Thread-38is 45
Value of x by Thread-80is 45
Value of x by Thread-16is 45
Value of x by Thread-62is 45
Value of x by Thread-54is 45
Value of x by Thread-7is 45
Value of x by Thread-17is 45
Value of x by Thread-21is 45
Value of x by Thread-90is 45
Value of x by Thread-29is 47
Value of x by Thread-5is 47
Value of x by Thread-85is 48
Value of x by Thread-34is 48
Value of x by Thread-42is 48
Value of x by Thread-31is 48
Value of x by Thread-25is 49
Value of x by Thread-11is 49
Value of x by Thread-36is 49
Value of x by Thread-58is 50
Value of x by Thread-46is 50
Value of x by Thread-40is 51
Value of x by Thread-24is 51
Value of x by Thread-35is 51
Value of x by Thread-4is 74
Value of x by Thread-43is 42

抛开操作x++x--的非原子性(其影响在OP的注释中有说明(不谈,还有另一种影响不依赖于单个操作的非原子化。

符号的一致性(所有输出都是正的(是因为,当一个线程完成循环并打印x的当前值时,仍然有许多其他线程到目前为止已经增加了x,但还没有减少它

这可以通过以下对代码的修改来证明:

private static class MyThreadCode implements Runnable {
private static AtomicInteger x = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 10000000; i++) {
x.incrementAndGet();
x.decrementAndGet();
}
System.out.println(x + "  " + Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(x + "  " + Thread.currentThread().getName());
}
}

您将看到,在10秒睡眠之前,所有线程在完成循环时都会打印一个正数(但其他线程仍在运行(。在10秒睡眠之后,所有线程都会打印最终结果,该结果始终为0,因为在10秒之后,所有的线程都完成了循环,并完成了与增量一样多的递减。

为了避免这个问题,您应该使用同步来确保增量和减量操作是原子式完成的。以下修改导致所有输出为0:

synchronized (x) {
x.incrementAndGet();
x.decrementAndGet();
}

(不过,如果您总是在同步块中使用x,那么您还可以将其作为一个基元int。(

最新更新