volatile关键字主要⽤于解决变量在多个线程之间的可⻅性
- 当写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量值刷新到主内存
- 当读一个volatile变量时,JMM会把线程对应的本地内存置为无效,再从主内存中读取该共享变量
编译过程
java代码编译后会变成java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在cpu上执行,所以java的并发机制依赖于JVM的实现和CPU指令
实现原理
java代码
1 | instance=new Singleton() |
转化成汇编代码
1 | 0x01a3deld:movb ...lock add1 .. |
volatile修饰的共享变量在进行写操作时会多出一个lock前缀指令,这个指令有2个作用
- 将当前处理器缓存行的数据写回到系统内存
- 写回操作会使其他cpu缓存了该内存地址的数据无效
为了提高处理速度,内存不会直接和cpu进行通信,而是先将系统内存读到内部缓存(L1,L2),但操作完不知道何时写回内存;
用volatile修饰的变量进行写操作,JVM会向处理器发送一条Lock前缀指令,进行缓存锁定:如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号(总线锁),而是锁住这块内存区域的缓存并写回内存(缓存所),并使用缓存一致性来确保修改的原子性,缓存一致性:会阻止同时修改由两个以上处理器缓存的内存区域数据
使用MESI控制协议维护内部缓存和其他处理器缓存的一致性,处理器使用嗅探技术来保证内部缓存,系统内存和其他处理器缓存的一致性
MESI协议(缓存一致性协议)
现在的处理器都是多核处理器,并且每个核都带有多个缓存(指令缓存和数据缓存,见下图)。为什么需要缓存呢,这是因为CPU访问内存的速度比较慢,所以在CPU和内存之间加了个缓存以提高访问速度。既然每个核都有缓存,那么假设两个核或者多个核同时访问同一个变量时这些缓存是如何进行同步的呢(缓存细分为一个个缓存行),这就有了MESI协议。
缓存行的四个状态:
MESI中每个缓存行都有四个状态,分别是E(exclusive)、M(modified)、S(shared)、I(invalid)。下面我们介绍一下这四个状态分别代表什么意思。
M:代表该缓存行中的内容被修改了,并且该缓存行只被缓存在该CPU中。这个状态的缓存行中的数据和内存中的不一样,在未来的某个时刻它会被写入到内存中(当其他CPU要读取该缓存行的内容时。或者其他CPU要修改该缓存对应的内存中的内容时(个人理解CPU要修改该内存时先要读取到缓存中再进行修改),这样的话和读取缓存中的内容其实是一个道理)。
E:E代表该缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。这个状态的缓存行中的内容和内存中的内容一致。该缓存可以在任何其他CPU读取该缓存对应内存中的内容时变成S状态。或者本地处理器写该缓存就会变成M状态。
S:该状态意味着数据不止存在本地CPU缓存中,还存在别的CPU的缓存中。这个状态的数据和内存中的数据是一致的。当有一个CPU修改该缓存行对应的内存的内容时会使该缓存行变成 I 状态。
I:代表该缓存行中的内容时无效的。