compareAndSet不成功操作的内存效果

时间:2015-04-24 11:28:48

标签: java concurrency java-memory-model compare-and-swap

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

boolean compareAndSet(expected,update)

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

  

compareAndSet和所有其他读取和更新操作   例如getAndIncrement具有两者的记忆效应   读写易变变量。

这肯定适用于成功的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(和程序)肯定会终止吗?

2 个答案:

答案 0 :(得分:1)

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

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

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

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

答案 1 :(得分:0)

我喜欢霍尔格的回答,并且由于技术信息会接受,但我会用相同的结果写一个答案,但不同的观点。这个程序有可能永远运行吗?是的,请考虑可能的编译器重新排序。

    new Thread(() -> {
        if(x == 0){
            while (true) {
                ai.compareAndSet(0, 0); // returns false
            }
        }
    }, "T1").start();

这可能是重新订购吗?是的。尽管有一个后续的易变商店,但没有规则说这里的升降机不会发生。这里唯一的规则是无法在易失性存储下面执行读取。

编辑:我意识到问题是关于记忆效应,而不是编译器排序。我会留下这个答案,因为它可能有用,但并没有真正回答这个问题。