系统相关
首页 > 系统相关> > java – compareAndSet不成功操作的内存效果

java – compareAndSet不成功操作的内存效果

作者:互联网

Java通过其原子类公开CAS操作,例如

boolean compareAndSet(expected,update)

JavaDocs指定了compareAndSet操作的内存效果,如下所示:

compareAndSet and all other read-and-update operations
such as getAndIncrement have the memory effects of both
reading and writing volatile variables.

这肯定适用于成功的compareAndSet调用.但是如果compareAndSet返回false,记忆效应也会成立吗?

我会说不成功的compareAndSet对应一个易失性读取(因为在这种情况下必须访问原子实例的当前值),但我不明白为什么CAS应该在不成功的情况下执行特殊的内存屏障指令.

问题实际上是,一个不成功的CAS是否也建立了先发生过的关系.考虑以下程序:

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();
    }
}

线程T2(和程序)肯定会终止吗?

解决方法:

使用易失性读写建立先发生关系的问题是这种关系仅存在于写入和后续读取中.如果一个线程T1写入共享的volatile变量而另一个线程T2从同一个变量读取,则如果T2在T1写入之前读取该变量,则不会发生先发生关系.如果所有确定T1在T2读取之前是否写入的是线程调度,那么我们就没有任何保证.

在没有额外同步的情况下处理它的实际方法是评估T2已读取的实际值.如果此值明显表明T1已经写入了新值,那么我们就有了一个有效的先发生关系.这是使用volatile boolean fooIsInitialized标志或volatile int currentPhase计数器时的工作原理.很明显,如果写入的值与旧值相同或者从未实际写入新值,则此操作无效.

您的示例程序的问题在于它推测了线程调度.它假设T2最终执行cas动作,并且在T1中将进行后续迭代,其中下一个cas将创建一个before-before关系.但这不能保证.它可能不是直观易懂的,但是如果没有同步,T1的所有迭代都可能在T2的动作之前发生,即使循环是无限的.它甚至是一种有效的线程调度行为,让T1在将CPU时间分配给T2之前永远消耗100%的CPU时间,因为不保证在相同优先级的线程之间进行抢占式线程切换.

但即使底层系统确实将CPU时间分配给最终执行操作的T2,也不需要JVM将其显示给T1,因为T2无法观察到T1曾经运行过的T1.它不太可能在现实生活中发现这一点,但答案仍然是没有保证.当存在一系列行动使得它可以观察到T1运行(即改变其状态)时,情况会发生变化,但当然,这一系列动作会使cas过时.

标签:java,concurrency,compare-and-swap,java-memory-model
来源: https://codeday.me/bug/20190708/1401125.html