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 (和程序)肯定会终止吗?
发布于 2015-05-05 12:19:27
在使用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运行时(即改变了其状态)时,事情就会发生变化,但是当然,这一系列的操作会使cas过时。
发布于 2015-05-05 14:23:30
我喜欢Holger的答案,而且由于技术信息的原因,我会接受这一点,但我会用相同的结果写出一个答案,但视角不同。这个程序有可能永远运行吗?是的,考虑可能的编译器重新排序。
new Thread(() -> {
if(x == 0){
while (true) {
ai.compareAndSet(0, 0); // returns false
}
}
}, "T1").start();这有可能是重新订购吗?是的,是这样的。没有规定说,这里的提升机不能发生,尽管随后的易失性商店。这里唯一的规则是读取不能在易失性存储下面执行。
编辑:我意识到问题是关于内存效果,而不是编译器排序。我会留下这个答案,因为它可能是有用的,但并没有真正回答问题。
https://stackoverflow.com/questions/29846319
复制相似问题