线程缓存和 Java 内存模型
Posted
技术标签:
【中文标题】线程缓存和 Java 内存模型【英文标题】:Thread Caching and Java Memory model 【发布时间】:2013-12-31 10:07:07 【问题描述】:我正在尝试了解 Java 内存模型和线程。据我了解,每个线程都有一个“主”内存的本地副本。因此,如果一个线程试图更改某个对象的 int
变量,例如,它会缓存 int
变量,如果它更改它,其他线程可能看不到更改。
但是如果线程缓存一些对象而不是 int 呢?在这种情况下,哪些线程会缓存它?如果一个线程缓存了一个对象的引用,那么其他线程看不到对象状态的任何更改?为什么?
【问题讨论】:
【参考方案1】:================================================ ===============
下面的答案是错误的,原因有很多。请不要用于任何其他目的,除了有一个爆炸。现代 CPU 上的缓存始终是一致的。
================================================ ===============
线程没有内存的本地副本。线程读取/写入的部分内存可能来自缓存,而不是主内存。缓存不需要彼此同步,也不需要与主内存同步。所以这是你可以观察到不一致的地方。
因此,如果一个线程尝试更改某个 int 变量,例如某个对象,它会缓存该 int 变量,如果它更改它,其他线程可能看不到更改。
没错。 Java 内存模型定义在发生在规则之前,例如在字段 x 的易失性写入和字段 x 的易失性读取之间存在先于规则。所以当写入完成后,后续的读取会看到写入的值。
如果没有这种先发生关系,所有的赌注都会被取消(当规则之前没有发生时,指令重新排序也会使生活变得复杂)。
如果线程缓存了对对象的引用,那么对对象状态的任何更改对其他线程也是不可见的?为什么?
它可能是可见的.. 它也可能是不可见的。如果没有发生之前的规则,所有的赌注都是。原因是否则很多优化,如硬件技巧来加快速度,或者编译器技巧是不允许的。当然,始终保持内存与缓存同步会降低性能。
================================================ =============
【讨论】:
请注意,“后续”与“写入后发生”不同。volatile
不提供任何及时性保证,它只是关于一致性,永远不会观察到乱序的写入。
@pveentjer 你为什么说喜欢 - 它可能是可见的......它也可能不可见?只有在线程本地堆栈中缓存的引用。因此,更改应该跨线程可见。我错了吗 ?硬件/编译器技巧 - 请您提供更清晰的图片。
@Kiran JMM 适用于 any 变量,对象引用在任何方面都不特殊。构成对象状态的只是一堆变量。【参考方案2】:
CPU 有不同级别的缓存 L1、L2、L3。每个 CPU(以及 /may CPU Core)都有自己的缓存。此缓存存储最小的主内存 (RAM) 集以提高性能。
_______________ ______________
| CPU 1 | | CPU 2 |
| _________ | | _________ |
| | Level 1 | | | | Level 1 | |
| | Cache | | | | Cache | |
| | | | | | | |
| |_________| | | |_________| |
|_______________| |______________|
| | | |
| | | |
_|_|______________|_|__
| |
| MAIN MEMORY |
|_______________________|
Time Command CPU 1 (Cache) CPU 2 (Cache) Main Memory
------- ---------- ---------------- -------------- -------------
1 --- --- --- x = 10
2 Read x (on cpu1) x = 10 --- x = 10
3 Write x <--20 (on cpu1) x = 20 --- x = 10
4 Read x (on cpu2) x = 20 x = 10 x = 10
5 put cache to Main mem x = 20 x = 10 x = 20
例如,上面的执行顺序,CPU2 上的 x 值是错误的。 x 值已被 CPU1 更改。 如果 x 变量被定义为 volatile,所有的写操作都会立即反映到主存。
【讨论】:
这是不正确的。缓存始终是连贯的,因此在 CPU 向缓存提交值后,不同的 CPU 仍然可以看到旧值是不可能的。像 MESI 这样的缓存一致性算法确保您的上述解释永远不会发生。因此,根本不需要将 volatile 值写入主存储器。可能是它无限期地留在缓存中。有关缓存实现和内存模型的更多信息,请查看以下书籍(免费):morganclaypool.com/doi/abs/10.2200/S00346ED1V01Y201104CAC016【参考方案3】:CPU 有多个缓存。正是这些硬件缓存可能具有不一致的数据副本。它们可能不一致的原因是,保持一切一致会使您的代码减慢 10 倍,并破坏您从拥有多个线程中获得的任何好处。要获得不错的性能,您需要有选择地保持一致。 Java 内存模型描述了它何时会确保数据一致,但在最简单的情况下不会。
注意:这不仅仅是 CPU 问题。可以在代码中内联一个不必在线程之间保持一致的字段。这可能意味着,如果一个线程更改了值,另一个线程可能永远不会看到此更改,因为它已被刻录到代码中。
【讨论】:
@Andremoniy JLS 讨论了虚拟机的寄存器堆栈。它没有谈论 CPU 的实际寄存器或缓存,因为这些是实现细节。 哇,非常感谢。这是非常重要的一点。那我敢请你看这个问题吗? ***.com/questions/53263594/… 我只知道一个具有不连贯高速缓存的微处理器,那就是 GPU。否则缓存总是一致的。【参考方案4】:“然而,在你可以写出体面的多线程代码之前,你确实需要更多地研究多线程代码的复杂性和微妙之处。
在线程方面,保证很少。
你能想象当两个不同的线程访问一个类的单个实例时可能发生的破坏,两个线程都调用该对象上的方法......并且这些方法修改了对象的状态? ... 太可怕了,甚至无法想象。”,来自 Sun Certified Programmer for Java 6,第 9 章:线程。
我的朋友,
在 Java 中,线程不会缓存任何对象或变量,它们只是拥有一个对 对象的实例的引用。谈论线程缓存更像是在谈论操作系统线程……Java 在所有操作系统中的工作方式都是一样的,不管线程在内部是如何管理的,这是不同的很大程度上取决于不同的操作系统。
看看这段代码:
AccountDanger r = new AccountDanger();
Thread one = new Thread(r):
Thread two = new Thread(r);
如您所见,在这种情况下,线程可以访问同一个实例:r。那么,你会遇到同步问题,当然......如果我们谈论本地或对象成员,线程一和二将可以访问所有r 的成员(如果它们可通过范围或 setter/getter 访问),它们将直接从 r 实例中读取值。这是肯定的,即使你没有注意到它,这有时真的很难。
如果您想编写多线程应用程序,我建议您阅读java 范围 和java 同步。
问候,
【讨论】:
以上是关于线程缓存和 Java 内存模型的主要内容,如果未能解决你的问题,请参考以下文章