Java volatile关键字是如何工作的?

Java volatile关键字是如何工作的?,java,multithreading,volatile,Java,Multithreading,Volatile,我正在阅读关于volatile关键字的文章。在阅读了volatile关键字后,通过下面的示例了解更多信息 public class TaskRunner { private static int number; private static boolean ready; private static class Reader extends Thread { @Override public void run() {

我正在阅读关于
volatile
关键字的文章。在阅读了
volatile
关键字后,通过下面的示例了解更多信息

public class TaskRunner {
 
    private static int number;
    private static boolean ready;
 
    private static class Reader extends Thread {
 
        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
 
            System.out.println(number);
        }
    }
 
    public static void main(String[] args) {
        new Reader().start();
        number = 42;
        ready = true;
    }
}
据我所知,在Java应用程序中,多个线程可以随时访问共享数据结构。有些是第一次写入,有些是更新,有些是读取,等等

因此,当这些发生时,每个线程只从主存访问
共享数据结构的
值。但有时,线程在
共享数据结构上的操作值会保留在缓存中,直到操作系统不将其放入主内存。因此,在该持续时间内,如果任何其他线程访问共享数据结构,将不会获得更新的值,该值由最后一个线程更新,并且仍在其缓存中

Volatile
被使用,一旦共享数据结构值被更改,在被任何其他线程访问之前,应该首先将其移动到主存。这是正确的理解吗

使用
volatile
,线程仍然没有得到更新值的情况是什么

但有时,线程在共享数据结构上的操作值仍保留在其缓存中,直到操作系统不将其放入主内存。因此,在该持续时间内,如果任何其他线程访问共享数据结构,将不会获得更新的值,该值由最后一个线程更新,并且仍在其缓存中

这不是操作系统。JVM使用一条CPU指令来重置CPU缓存。老实说,这种说法也是不正确的,因为Java内存模型对此类指令一无所知。这是实现
volatile
行为的方法之一

但有时,线程在共享数据结构上的操作值仍保留在其缓存中,直到操作系统不将其放入主内存。因此,在该持续时间内,如果任何其他线程访问共享数据结构,将不会获得更新的值,该值由最后一个线程更新,并且仍在其缓存中

这不是操作系统。JVM使用一条CPU指令来重置CPU缓存。老实说,这种说法也是不正确的,因为Java内存模型对此类指令一无所知。这是实现
volatile
行为的方法之一


Java是相当高级的:作为一种语言,它不是为任何特定的CPU设计而设计的。此外,java编译成字节码,这是一种中间产品:java不提供,也没有让您编写特定于CPU体系结构的低级操作的目标

然而,缓存是一个特定于CPU体系结构的低级概念。当然,几乎每个现代CPU都有,但谁知道20年后会发生什么呢

因此,根据volatile对CPU缓存的作用,将其放入volatile会跳过一些步骤

volatile
对java代码有影响。目前,我所知道的大多数虚拟机都通过向CPU发送一些关于刷新缓存的指令来实现这种效果

最好是在java级别本身处理volatile,而不是在“大多数虚拟机都是这样实现的”级别——毕竟,这是可以改变的

java的设置方式基本上如下所示:

如果java中任何地方的任何两行代码之间都没有关系,那么您应该假设java就像薛定谔猫:每个线程在整个VM中加载的每个对象上都有或没有每个字段的本地缓存副本,无论何时您写或读任何内容,宇宙掷硬币,用它来决定你是否得到了副本,并且总是掷硬币来捣乱你。在您自己的机器上进行测试时,硬币会翻转以使测试通过。在crunch weekend的生产过程中,当数百万美元处于生产线上时,它会翻转,使您的代码失败

唯一的出路是确保你的代码不依赖于投币

实现这一点的方法是使用comes-before规则,您可以在Java内存模型中查看这些规则

volatile是添加它们的一种方法

在上面的代码中,如果没有volatile,读卡器线程可能总是使用其
ready
的本地副本,因此永远不会准备就绪,即使主设置
ready
为true已经过了很多小时。实际上这不太可能,但是JMM说虚拟机可以在这里投币:它可能会让你的读卡器线程几乎立即继续,它可能会保持一个小时,它可能会永远保持。一切合法——这一准则被打破,其行为取决于抛硬币,这是不好的

然而,一旦你们引入了volatile,你们就建立了一种“先到先得”的关系,现在你们保证读者继续阅读。实际上,volatile在这样标记的变量上禁用了coinflip,并且在读取/写入事件之前建立了coinflip:

如果一个线程在一个可变变量中观察到一个更新的值,那么在该变量更新的线程代码之前运行的所有行都与读取更新的线程中在该代码之后运行的所有行具有“先到先”关系

因此,需要明确的是:

在这里没有任何易变标记,虚拟机允许读卡器永久挂起是合法的。虚拟机允许读卡器继续运行也是合法的(让它观察到
就绪
现在是
真实
,而读卡器仍然看到
数字
是0(而不是42),即使它通过了就绪检查!-但它不必,它也允许虚拟机让读卡器从未通过就绪检查,或者让它通过就绪检查并观察42。虚拟机可以随心所欲地去做它想做的事情;对于CPU、体系结构和月球相位的这一特定组合,现在任何看起来最快的事情都可以

使用volatile,读卡器很快就会继续,一旦读卡器继续,它肯定会观察到
42