Java - 之前发生 - 易变

Dha*_*amy 4 java multithreading java-memory-model happens-before

我有以下代码

class VolatileCount {
    volatile int count;
    Object lock = new Object();

    public void increment() {

        synchronized (lock) {
            count = count + 1;
        }
        System.out.print(" " + count);
    }

}
Run Code Online (Sandbox Code Playgroud)

如果我increment()从多个线程调用相同的对象,我会得到以下输出(可能在您的机器上有所不同)

2 3 2 5 4 8 8 6 11 13 10 9 15 14 12 20 19
Run Code Online (Sandbox Code Playgroud)

看看我认为发生的重复数字似乎被破坏了,因为考虑前三个数字(2 3 2),如果线程看到3,则发生增量,并且因为变量是易变的,所以它的值应该是3或更多但是在任何线程中都不能为2.
但是,打印线似乎已在这里重新排序,重新排序该行是否正确?我在这里错过了什么?我运行JDK 7(Eclipse)

dca*_*tro 7

更新

因为你似乎想要解释"2 3 2"的具体情况

  • X增量i(现在为1)
  • Y增量i(现为2)
  • X读取i(X加载2)
  • Y读i(Y加载2)
  • Y打印先前加载的值(打印2)
  • Z增量i,读数i,打印i(打印3)
  • X打印先前加载的值(打印2)

关键是:System.out.print(" " + count)不是原子的.执行易失性读取之后和打印值之前,线程可以被抢占.

如果要防止打印重复值,则必须在锁执行volatile读取:

public void increment() {
    int localCount;
    synchronized (lock) {
        count = count + 1;
        localCount = count; // volatile load
    }
    System.out.print(" " + localCount);
}
Run Code Online (Sandbox Code Playgroud)

这不会阻止值无序打印.为了按顺序打印它们,没有重复,你也必须移动print到锁中.

老答案

print语句在锁外.考虑一下里面 运行的代码System.out.print(" " + count).

  • 线程X递增 i
  • 线程X计算print参数并对变量执行易失性读取count,并加载该值2.
  • 线程X被线程Y抢占,线程Y递增 i
  • 线程Y加载i(现在是3),调用运行完成的print方法.
  • 线程Y被抢占,线程X现在运行print完成,打印2.

这会使数字显示不按顺序,例如"3 2 4".

在下列情况下,某些数字也可能重复

  • 线程X递增i(现在为2)
  • 线程Y增量i(现在为3)
  • 线程X打印i(3)
  • 线程Y打印i(3)