Java内存模型 - 有人可以解释它吗?

2022-09-01 08:19:42

多年来,我一直试图理解Java规范中处理内存模型和并发性的部分。我不得不承认,我失败得很惨。是的,我了解锁和“同步”以及wait()和通知()。我可以很好地使用它们,谢谢。我甚至对“不稳定”的作用有一个模糊的想法。但所有这些都不是来自语言规范 , 而是来自一般经验。

以下是我问的两个示例问题。我对特定的答案不太感兴趣,因为我需要了解答案是如何从规范中得出的(或者可能是我如何得出结论,规范没有答案)。

  • “挥发性”究竟是做什么的?
  • 对变量的写入是原子的吗?它是否取决于变量的类型?

答案 1

我不打算在这里实际回答你的问题 - 相反,我会将你重定向到我看到推荐的关于这个主题的建议的书:Java并发实践

一个警告:如果这里有答案,预计其中相当多的答案是错误的。我不打算发布细节的原因之一是,我很确定我至少在某些方面犯错。当我说每个认为自己可以回答这个问题的人实际上有足够的严谨性来纠正它时,我对社区没有任何不尊重的意思,这几乎是零。(Joe Duffy最近发现了一些令.NET内存模型感到惊讶的东西。如果他能弄错,像我们这样的凡人也会犯错。


我将只就一个方面提供一些见解,因为它经常被误解:

波动性和原子性之间是有区别的。人们通常认为原子写入是易失性的(即,如果写入是原子的,则无需担心内存模型)。那不是真的。

波动性是关于执行读取的一个线程(逻辑上,在源代码中)是否会“看到”另一个线程所做的更改。

原子性是关于如果看到变化,是否有机会只看到部分变化。

例如,以写入整数字段为例。这保证是原子的,但不是不稳定的。这意味着如果我们有(从foo.x = 0开始):

Thread 1: foo.x = 257;
Thread 2: int y = foo.x;

可以是 0 或 257。由于原子性约束,它不会是任何其他值(例如256或1)。但是,即使您知道在“墙时间”中线程2中的代码在线程1中的代码之后执行,也可能存在奇怪的缓存,内存访问“移动”等。使变量易失性将解决此问题。yx

剩下的就交给真正的诚实至善的专家了。


答案 2
  • 非变量可以在本地缓存线程,因此不同的线程可以同时看到不同的值; 防止这种情况(volatilevolatile)
  • 写入32位或更小的变量保证是原子的(此处暗示);对于 和 来说不是这样,尽管 64 位 JVM 可能将它们实现为原子操作longdouble

推荐