Dom*_*nik 5 java concurrency java-memory-model compare-and-swap
Java通过其原子类公开CAS操作,例如
boolean compareAndSet(expected,update)
Run Code Online (Sandbox Code Playgroud)
JavaDocs指定了compareAndSet 操作的内存效应,如下所示:
CompareAndSet 和所有其他读取和更新操作(例如 getAndIncrement)都具有读取和写入 易失性变量的记忆效应。
这绝对适用于成功的compareAndSet调用。但是如果compareAndSet返回,记忆效应也成立吗false?
我想说,不成功compareAndSet对应于易失性读取(因为在这种情况下必须访问原子实例的当前值),但我不明白为什么 CAS 应该在不成功的情况下执行特殊的内存屏障指令。
问题实际上是,不成功的 CAS 是否也建立了happens-before关系。考虑以下程序:
public class Atomics {
private static AtomicInteger ai = new AtomicInteger(5);
private static int x = 0;
public static void main(String[] args) {
new Thread(() -> {
while (x == 0) {
ai.compareAndSet(0, 0); // returns false
}
}, "T1").start();
new Thread(() -> {
x = 1;
ai.compareAndSet(0, 0); // returns false
}, "T2").start();
}
}
Run Code Online (Sandbox Code Playgroud)
线程T2(和程序)肯定会终止吗?
使用读取和写入建立发生前关系的问题volatile在于,这种关系仅针对写入和后续读取而存在。如果一个线程 T1 写入共享volatile变量,而另一个线程 T2 读取同一变量,则如果 T2 在 T1 写入该变量之前读取该变量,则可能存在“发生之前”关系。如果决定 T1 是否在 T2 读取之前写入的是线程调度,那么我们就没有任何保证。
无需额外同步即可处理该问题的实用方法是评估 T2 已读取的实际值。如果这个值表明 T1 已经写入了新值,则我们有一个有效的happens-before关系。这就是使用volatile boolean fooIsInitialized标志或volatile int currentPhase计数器时的工作原理。很明显,如果写入的值与旧值相同或者新值从未实际写入,则此方法无法工作。
您的示例程序的问题在于它推测了线程调度。它假设 T2 最终执行 cas 操作,并且 T1 中将有一个后续迭代,其中下一个 cas 将创建一个happens-before关系。但这并不能保证。这可能无法直观地理解,但如果没有同步,T1 的所有迭代都可能在 T2 的操作之前发生,即使循环是无限的。这甚至是一种有效的线程调度行为,让 T1 永远运行,消耗 100% CPU 时间,然后再将 CPU 时间分配给 T2,作为同等优先级线程之间的抢占式线程切换这甚至是一种有效的线程调度行为,让 T1 永远运行,在将 CPU 时间分配给 T2 之前消耗 100% CPU 时间,因为不能保证
\n\n但是,即使底层系统确实将 CPU 时间分配给 T2(最终将执行操作),JVM 也不需要向 T1 表明这一点,因为 T1 无法观察到 T2 曾经运行过的\xe2\x80\x99 。\xe2\x80\x99 在现实生活中不太可能发现这一点,但答案仍然是没有保证。当存在一系列操作使得 T1 可以观察到 T2 运行的情况(即更改其状态)时,情况就会发生变化,但是当然,该操作链会使 cas 过时。
\n| 归档时间: |
|
| 查看次数: |
440 次 |
| 最近记录: |