Java volatile型变量何时才从主内存中获取?

根据<<深入理解Java虚拟机>>第12章的12.3.3节结尾部分的描述

假定T表示一个线程,V和W分别表示两个volatile型变量,那么在进行read、load、useassign、store和write操作时需要满足如下规则:

只有当线程T对变量V执行的前一个动作是load的时候,线程T才能对变量V执行use动作;
并且,只有当线程T对变量V执行的后一个动作是use的时候,线程T才能对变量V执行load动作。线程T对变量V的use动作可以认为是和线程T对变量V的load、read动作相关联的,必须连续且一起出现。

  • 这条规则要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V所做的修改。

又看到许多文章中说, Java内存模型实际上基于MESI协议

那么volatile型变量是每次都从主内存中重新获取变量, 还是基于MESI 协议, 只有当前处理器嗅探到了缓存中存储的值已经被修改而无效, 才从主内存中获取呢

阅读 2.6k
3 个回答
MESI协议的核心思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

在Java的内存模型中有工作内存的概念。Java内存模型规定所有的变量都是存在主存当中(类似于物理内存),每个线程都有自己的工作内存(类似于CPU高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

使用volatile关键字会强制将修改的值立即写入主存。因此假设有两个线程同时修改共享变量A,线程1修改会导致线程2中的工作内存缓存失效,所以线程2再次读取变量时会到主存中获取。

volatile关键字就是通过这种机制来保证可见性的。

这个问题,得穿透jvm,直接从底层谈起。

对 volatile 的写操作,jvm编译的时候,会在前面加一个 lock 前缀的汇编指令。你可以查下 lock 指令,如果你想详细了解。

lock 指令会引发以下两个动作:

  1. 引发总线锁定(不一定,得看缓存行的状态,Exclusive和Modified则不会)
  2. 强制刷新storeBuffer,到缓存和内存


「并发编程的艺术」中说,volatile 有 synchronize 的语意,就是因为第一个动作。而保证可见性的主要原因之一,就是第二个动作。另外一个原因是ESMI协议,变成Invalid状态的缓存行,需要重新拉取。

每一个cpu都有一个storeBuffer,在store的时候,并不会立即存入缓存,而是先存入storeBuffer,择机再存入缓存。而每个cup的storeBuffer,都是只对自己可见的,其他cpu无从得知(也就是无法通过总线获取),这就是造成不可见的根源!造成不可见的根源!造成不可见的根源!

所以,强制刷新下来,就可见了。


何时从主存获取?

当一个volatile变量,被修改后,store时会存入storeBuffer。然后,强制把storeBuffer拉到缓存里,再从缓存写进内存里。然后当前的cpu会发信号给总线,通知其他cpu,这个volatile变量所在行,已经被改了,需要设置为失效(EMSI协议)。

其他cpu中的线程,要read这个volatile变量时,不得不重新拉数据。首先通过总线,去其他cpu缓存中找。没有的话,就得从内存拉。

因为之前,改volatile变量时,已经把缓存、内存里的变量更新了。再拉的都是新值。

MESI协议,是通过有限状态机的一系列操作,才让缓存达到最终一致性。但并不是强一致性,针对并行程序,依然不够用。volatile 又加一层保障,属于强一致。而且这两种作用的地方也不一样,MESI主要是作用cpu,JMM主要作用于内存。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏