我正在尝试理解Java内存模型和线程。据我所知,每个线程都有一个“主”内存的本地副本。因此,如果一个线程尝试更改某个对象的int
变量,它会缓存int
变量,如果它更改了变量,则其他线程可能看不到更改。
但是如果线程缓存某个对象而不是int呢?在这种情况下,线程会缓存什么?如果一个线程缓存对对象的引用,则任何对对象状态的更改都不可见于其他线程?为什么?
我正在尝试理解Java内存模型和线程。据我所知,每个线程都有一个“主”内存的本地副本。因此,如果一个线程尝试更改某个对象的int
变量,它会缓存int
变量,如果它更改了变量,则其他线程可能看不到更改。
但是如果线程缓存某个对象而不是int呢?在这种情况下,线程会缓存什么?如果一个线程缓存对对象的引用,则任何对对象状态的更改都不可见于其他线程?为什么?
CPU有不同级别的缓存L1、L2和L3。每个CPU(以及可能的CPU核心)都有自己的缓存。这些缓存为性能存储了最小的主存储器(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有多个缓存。这些硬件缓存可能会有不一致的数据副本。它们可能不一致的原因是,保持一致会使您的代码速度减慢10倍,并破坏您从多个线程获得的任何好处。要获得良好的性能,您需要选择性一致性。Java内存模型描述了什么时候确保数据一致,但在简单情况下并不如此。
注意:这不仅是CPU问题。一个不需要在线程之间保持一致的字段可以内联在代码中。这意味着如果一个线程更改了值,另一个线程可能永远不会看到这个更改,因为它已被固化到代码中。
=============================================================
以下答案有很多错误。请不要将其用于任何其他目的,只是为了好玩。现代CPU上的缓存总是一致的。
=============================================================
一个线程没有内存的本地副本。线程读取/写入的部分内存可能来自缓存,而不是主内存。缓存不需要彼此同步,也不需要与主内存同步。因此,这就是您可以观察到不一致性的地方。
因此,如果一个线程尝试更改某个对象的int变量,它会缓存int变量,如果更改了其他线程可能无法看到更改。
那是对的。 Java内存模型在先前发生规则中定义,例如,volatile对字段x的写入和对字段x的volatile读取之间存在先前发生规则。因此,当写入完成时,后续的读取将看到写入的值。
如果没有这样的先前发生关系,则一切都无法确定(当没有先前发生规则时,指令重排也会使事情变得复杂)。
如果线程缓存了对对象的引用,则对对象状态的任何更改对其他线程也不可见? 为什么?
它可能是可见的...也可能不可见。如果没有先前发生规则,一切都无法确定。原因是否则许多优化(例如硬件技巧来加快速度或编译器技巧)都将不被允许。当然,始终保持内存与缓存同步会降低性能。
===========================================================
volatile
无法提供任何时间性保证,它只能保证一致性,即不会出现观测到的写入顺序混乱。 - Marko Topolnik然而,在您编写良好的多线程代码之前,您真正需要研究更多关于多线程代码的复杂性和微妙之处。
当涉及到线程时,很少有什么是保证的。
您能想象出当两个不同的线程访问单个类实例并且这些线程调用该对象上的方法且这些方法修改对象状态时可能发生的混乱吗?…即使想象都太可怕了。" ——摘自《Java 6认证程序员》第9章:线程。
我的朋友,
在Java中,线程不会缓存任何对象或变量,它们只是对一个对象实例的引用。谈论线程缓存内存更像是谈论操作系统线程... Java在所有操作系统中以相同的方式工作,无论线程在内部如何管理,这因不同的操作系统而异。
看一下这段代码:
AccountDanger r = new AccountDanger();
Thread one = new Thread(r):
Thread two = new Thread(r);