sil*_*nce 6 java concurrency jvm
在作为 JSR166 的一部分引入的类中,作者使用所谓的填充来填充 Striped64.Cell 类的单值字段。
以下是该课程的摘录:
/**
* Padded variant of AtomicLong supporting only raw accesses plus CAS. The value field is placed
* between pads, hoping that the JVM doesn't reorder them.
* <p/>
* JVM intrinsics note: It would be possible to use a release-only form of CAS here, if it were
* provided.
*/
static final class Cell {
volatile long p0, p1, p2, p3, p4, p5, p6;
volatile long value;
volatile long q0, q1, q2, q3, q4, q5, q6;
...
Run Code Online (Sandbox Code Playgroud)
然后作者使用 CAS 原子地修改该值。
在 Striped64 类中,作者还使用 Unsafe 来访问其他两个字段,但没有应用任何此类填充。
我的问题是:为什么需要做这样的事情,引入 14 个冗余字段来填充单个值字段?
填充的目的是防止value共享字段的缓存行 - 否则可能必须从内存中重新获取该值,因为缓存行上的其他内容需要使整行无效。所以目标是提高性能。
为了让事情变得更简单,Java 8 引入了注释@Contended,它在幕后做同样的事情,只不过它是由 JVM 本身处理的。
虽然我确实同意阿西利亚斯的回答,但我认为这需要一些解释。
为什么缓存未命中很重要?
因为从主存读取比从缓存读取要慢得多。如果您有一个需要经常使用的变量,将其缓存很重要。此外,如果此变量与其他变量共享相同的缓存,则可能会出现整个缓存行无效的情况。
考虑变量 1 与变量 2 位于同一缓存中的示例。变量1由线程1使用,变量2由线程2使用。因为它们位于同一缓存行上,所以如果变量 2 有更新并且线程 1 需要使用变量 1,则需要删除缓存行(即使它不使用该变量!)并从主内存读取。这就是所谓的虚假分享。
为什么实际上还有 7 个多头?
如果仅当 JVM 不决定重新排序内存时,那么从哪里开始读取此变量并不重要(您可以从 8 个“缓存行”中的第三个读取它) - 您将最终在缓存行中仍然只有一个值。因此,无论您从何处开始读取,缓存行中都只有一个对您重要的值,因此不可能出现“错误共享”的“缓存未命中”。
PS 这就是为什么 Java 对象的大小可以被 8 整除。