如果我们考虑以下类和x86架构:
class Foo{
int x;
void increment() {
compareAndSwap(x, x+1);
}
int getX() { return x; }
}
如果我们现在假设一个线程正在调用increment,而另一个线程定期调用get,那么读者是否有可能看不到X的更新版本?不应该比较和消息实际上在缓存中锁定缓存行,在比较后更新值然后触发一个完整的屏障吗?
答案 0 :(得分:1)
我认为答案是:它取决于。
在底层的jvm实现 - 分别是你引入的 compareAndSwap 而没有进一步的定义。
换句话说:我认为期望对compareAndSwap的正确实现应该考虑缓存行是合理的。
这就是jvm给你保证的全部意义,不是吗。如果jvm由于CPU缓存而产生不一致的结果 - 这样的保证会给我们提供什么样的帮助?!
含义:Java内存模型的全部意义在于提供一定的" cast in stone"担保。如果CPU缓存可以破坏它们,它们将毫无用处。
但我同意所给出的评论 - 变量不是 volatile 的事实意味着不同的线程可能会看到"不同的" x的值。
答案 1 :(得分:1)
读者线程绝对有可能看不到x
的更新值。确保您获得最新x
是volatile
的主要目的之一。
JLS的第17章谈到了这一点。具体来说,请查看:
如果至少有一次访问是写入,则对同一变量的两次访问(读取或写入)被认为是冲突的。
当一个程序包含两个冲突的访问(第17.4.1节)时,这些访问不是由一个先发生的关系排序的,而是说它包含一个数据竞争。
我不会列出你如何建立一个先发生关系的所有细节(在17.4.5中详细说明),但足以说明,在没有同步的情况下访问非易失性字段不会这样做。
这意味着您的代码有数据竞争,这意味着许多赌注都已关闭。例如,JVM不需要为操作提供单个顺序排序;你可以让一个线程看到一个订单,另一个线程看到另一个订单。
引用我的最后一点JLS,这次来自17.4.3:
顺序一致性是对程序执行中的可见性和排序的有力保证。在顺序一致的执行中,所有单个操作(例如读取和写入)的总顺序与程序的顺序一致,并且每个单独的操作都是原子并且对于每个线程立即可见。 (重点补充)
这就是你想要的,如果你的代码没有数据竞争,你只能保证拥有它。这与你的问题有关,因为没有顺序一致的执行,读者线程基本上被允许将事物命令为“所有其他动作(包括读取),然后是写入” - 意味着它永远不会看到读取 - 即使是其他线程继续进行而不会将写入延迟到无穷大。
当人们尝试创建一个boolean stop
变量来表示某个线程应该停止,但忽略使其变得不稳定时,通常会出现这种情况。有时它有效;很多时候,该线程将永远旋转,因为它永远不会看到对stop
的写入。
另请参阅this answer有关使用volatile
关键字的信息。