0

我试图了解 Java 内存模型,但一直未能得到关于 CPU 缓存的观点。

据我所知,在 JVM 中,我们有以下位置来存储本地和共享变量:

local variables -- on thread stack

shared variables -- in memory, but every CPU cache has a copy of it

所以我的问题是:为什么将局部变量存储在堆栈上,并将(缓存)共享变量存储在 CPU 缓存中?为什么不反过来(假设 CPU 缓存太昂贵而无法存储两者),我们将局部变量缓存在 CPU 缓存中,然后从内存中获取共享变量?这是 Java 语言设计或计算机体系结构的一部分吗?

进一步:就像“CPU 缓存”听起来一样简单,如果多个 CPU 共享一个缓存怎么办?而在具有多级缓存的系统中,共享变量的副本将存储在哪一级缓存中?此外,如果多个线程在同一个 CPU 内核中运行,是否意味着它们共享同一组缓存的共享变量,因此即使未定义共享变量volatile,对该变量的访问仍然是即时可见的到在同一 CPU 上运行的其他线程?

4

1 回答 1

1

“本地”和“共享”变量在代码上下文之外毫无意义。它们不会影响缓存状态的位置或什至缓存。根据您的状态存储位置来思考或推理甚至没有用。JMM 存在的全部原因是这样的细节不会暴露给程序员,这些细节因架构而异。通过依赖低级别的硬件细节,您对 JMM 提出了错误的问题。它对您的应用程序没有用处,它使它变得脆弱、更容易破解、更难推理并且更不便携。

也就是说,一般来说,您应该假设任何程序状态(如果不是所有状态)都有资格被缓存。事实是,缓存的内容实际上并不重要,任何东西都可以是,无论是原始类型还是引用类型,甚至是由多个字段封装的状态变量。无论线程运行什么指令(这些指令也因架构而异 - 请注意!),这些指令默认返回 CPU 以确定哪些与缓存相关,哪些不与缓存相关;程序员自己不可能做到这一点(尽管可以影响状态变量的缓存位置,看看什么是错误共享)。

同样,我们还可以对 x86 进行更多概括,即活动的原始类型可能放在寄存器上,因为 P/ALU 将能够以最快的速度使用它们。不过,其他任何事情都会发生。如果原语是核心本地的,则有可能将它们移动到缓存的 L1/2 中,当然它们很可能会很快被覆盖。如果 CPU 认为将来会有上下文切换,或者它不能,它可能会将状态变量放在共享的 L3 上。硬件专家将需要对此做出回应。

理想情况下,状态变量将存储在离处理器单元最近的高速缓存(寄存器、L1/2/3,然后是主存储器)中。不过,这由 CPU 决定。在 Java 级别上推理缓存语义是不可能的。即使启用了超线程(我不确定 AMD 的等效项是什么),线程也不允许共享资源,即使这样,如果是的话,请记住可见性并不是与共享状态变量相关的唯一问题;在处理器执行流水线的情况下,您仍然需要适当的指令来确保正确的顺序(即使在您摆脱 CPU 上的读/写缓冲之后也是如此),无论是这种情况hwsync还是适当的栅栏或其他。

同样,关于缓存属性的推理是没有用的,因为 JMM 会为您处理这些,并且因为它无法确定缓存的位置/时间/内容。此外,即使您确实知道何时/何地/什么问题,您仍然无法推断数据可见性;无论如何,所有缓存都以相同的方式处理缓存数据,您将需要依靠处理器更新 ME(O)SI 状态、指令排序、加载/存储缓冲、回写/直通等之间的缓存状态。 . 而且你还没有处理在操作系统和JVM级别上可能出现的问题。同样,幸运的是,JDK 允许您使用基本工具,例如volatilefinal,以及在所有平台上始终如一地工作并生成可预测且易于推理的代码的原子。

于 2017-12-25T05:07:27.097 回答