我正在尝试将写的Java volatile
关键字理解为带有CPU缓存的多线程程序中的易失性原子变量。
我已经阅读了几个教程和Java语言规范,特别是section 17.4.5 on "happens-before ordering"。我的理解是,当线程将新值写入volatile变量时,更新的值必须对读取该变量的其他线程可见。对我来说,这些语义可以通过以下两种方式之一实现:
线程可以在CPU缓存中缓存一个volatile变量,但是必须立即将对缓存中变量的写入刷新到主内存。换句话说,缓存为write-through。
线程永远不能缓存volatile变量,必须在主内存中读写这些变量。
此tutorial(http://tutorials.jenkov.com)中提到了方法1:
在Stackoverflow问题“Volatile variable in Java”中提及了方法2,并且此tutorial也提到了:通过声明计数器变量volatile,所有写入计数器 变量将立即写回主存。
此变量的值永远不会缓存在线程本地:all 读写将直接进入“主存”
Java中使用哪种方法正确?
相关的Stackoverflow问题不回答我的问题:
Does Java volatile read flush writes, and does volatile write update reads
答案 0 :(得分:8)
保证只是您在语言规范中看到的。从理论上讲,写一个volatile变量可能会强制缓存刷新到主内存,或者可能没有,可能是后续读取强制缓存刷新或以某种方式导致缓存之间没有缓存刷新的数据传输。这种模糊性是刻意的,因为如果更详细地阐述了易变量的机制,它允许未来可能的优化。
实际上,对于当前的硬件,它可能意味着,如果没有连贯的缓存,写入volatile变量会强制缓存刷新到主内存。当然,使用一致的缓存,不需要这样的刷新。
答案 1 :(得分:3)
在Java中,最准确的说是所有线程都会看到对volatile字段的最新写入,以及在易失性读/写之前的任何写入。
在Java抽象中,这在功能上等同于从共享内存中读取/写入的易失性字段(但这在较低级别上并不严格准确)。
远低于与Java相关的水平;在现代硬件中,任何和所有读/写任何和所有内存地址始终首先出现在L1和寄存器中。话虽如此,Java旨在隐藏程序员的这种低级行为,因此这只是在概念上与讨论相关。
当我们在Java中的字段上使用volatile
关键字时,这只是告诉编译器在对该字段的读/写中插入一些称为内存屏障的内容。记忆障碍有效地确保了两件事;
读取此地址的任何线程都将使用最新的值(屏障使它们等到最近的写入使其返回到共享内存,并且没有读取线程可以继续,直到此更新的值使得它到他们的L1缓存)。
对任何字段的读/写都不能越过屏障(也就是说,它们总是在其他线程可以继续之前写回来,并且编译器/ OOO无法将它们移动到屏障之后的某个点)。 / p>
给出一个简单的Java示例;
//on one thread
counter += 1; //normal int field
flag = true; //flag is volatile
//on another thread
if (flag) foo(counter); //will see the incremented value
基本上,在将flag
设置为true
时,我们会创建一个内存屏障。当线程#2尝试读取此字段时,它会进入我们的屏障并等待新值到达。同时,CPU确保在新值到达之前写回counter += 1
。因此,如果flag == true
,那么counter
将会增加。
总结一下;
所有线程都会看到最新的volatile字段值(可以大致描述为“读/写通过共享内存”)。
对易失性字段的读/写建立与先前对一个线程上任何字段的读/写的关系。