发生在线程和原子变量之间

Happens before between threads and atomic variable

假设一个 AtomicIntegerc 被两个线程 thread1 和 thread2 共享。 Thread1 使用 c.incrementAndGet() 设置(仅一次)易失性变量 t1。 Thread2 使用 c.incrementAndGet() 设置(仅一次)易失性变量 t2。一旦设置了 t1 和 t2,它们就不会被任何其他线程再次设置。假设thread1设置t1后,检查t2的值,得到null。是否保证 t2 随后设置为比 t1 更高的值? (反之亦然)。换句话说,下面的断言总是正确的吗?是为什么呢?

AtomicInteger c = new AtomicInteger();
volatile Integer t1=null;
volatile Integer t2=null;

//Thread1
t1=c.incrementAndGet();
if(t2 == null){
  assert t2==null || t2>t1;
}

//Thread2
t2=c.incrementAndGet();
if(t1==null){
  assert t1==null || t1>t2;
}

我相信这些断言是正确的,原因如下:如果 t1 通过递增 c 赋值,而 t2 尚未通过递增 c 赋值,那么当 t2 随后通过递增 c 赋值时,它必须大于 t1 的值。

更新:由于根据下面的正确答案,断言可能并不总是成立,我添加了第 2 部分问题:检查

不,它们并不总是正确的。无法保证线程 1 会在线程 2 之前 运行,或者操作不会交错。如果他们 运行 为:

  1. 线程 2 分配 t2 = 1
  2. 线程 2 执行其 if 检查,结果为真
  3. 线程 1 分配 t1 = 2
  4. 线程 2 执行其断言

...然后在第 3 步,线程 2 将看到 t1 != nullt2 > t1

线程 1 可能同样会失败。

(正如 JB Nizet 所提到的,即使我上面写的操作实际上也是由多个操作组成的。这种详细程度对于这个问题来说并不是绝对必要的,但它 养成将事情真正分解为各自的操作的好习惯,例如 incrementAndGet 与它进入的分配。当您想展示为什么某些东西不起作用时,经验会让您过滤掉它们,但是表明它 起作用,你确实需要考虑每个操作。)

不,不能保证。可能会发生以下情况:

  • 线程2:c.incrementAndGet(c为1,t2仍为null,但后面会初始化为1)
  • 线程1:c.incrementAndGet(c为2,t1仍为null,但后面会用2初始化)
  • 线程 1:t1 = 2
  • thread1: if (t2 == null): 条件为真。评估 if 块
  • 线程 2:t2 = 1
  • thread1: t2 == null: 条件为假,所以计算 or 的另一个操作数
  • thread1: t2 > t1: false 因为 t2 是 1 而 t1 是 2
  • 线程 1:断言:失败