发生在线程和原子变量之间
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 之前 运行,或者操作不会交错。如果他们 运行 为:
- 线程 2 分配
t2 = 1
- 线程 2 执行其
if
检查,结果为真
- 线程 1 分配
t1 = 2
- 线程 2 执行其断言
...然后在第 3 步,线程 2 将看到 t1 != null
和 t2 > 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:断言:失败
假设一个 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 之前 运行,或者操作不会交错。如果他们 运行 为:
- 线程 2 分配
t2 = 1
- 线程 2 执行其
if
检查,结果为真 - 线程 1 分配
t1 = 2
- 线程 2 执行其断言
...然后在第 3 步,线程 2 将看到 t1 != null
和 t2 > 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:断言:失败