volatile关键字原理解析

在多线程开发时,我们希望每个线程对共享变量的修改对其他线程可见,这时候我们会给共享变量加一个volatile关键字,那么它的底层是如何实现的,这篇博客我们就解析一下volatile的原理。

JMM

在此之前我们需要先了解一下java内存模型JMMvolatile关键字原理解析_第1张图片

每个线程访问共享变量时并不是直接访问主内存,而是先将共享变量拷贝到各自的工作内存,等线程结束后再将工作内存中的共享副本变量写回主内存。这些过程都是通过JMM的数据原子操作完成的。

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

volatile关键字原理解析_第2张图片

这张图描绘的是线程执行一个while(!initFlag)的死循环,需要线程2改变initFlag的Boolean值才会跳出(原子操作已标注),然而结果却是线程2改变initFalg并且写回主内存但线程1不知道值已经改变,它工作内存中的initFalg任然为false,所以死循环仍然继续,jmm缓存不一致问题,而解决这个问题就需要用到volatile。

volatile

在早期计算机中解决缓存不一致问题是通过总线加锁方式。CPU在读取数据到高速缓存时,就给数据加锁,其他线程无法访问,直到CPU执行完程序将数据写回主内存时,才将锁释放,其他线程才可以访问,显然这种方式性能太低,多个线程变成串行执行。为解决这一问题,总线引入了MESI缓存协议。volatile正是基于这种协议。
volatile关键字原理解析_第3张图片

1.当线程2修改完volatile修饰的共享变量后会立即写回主内存,这时JVM就会向CPU发送LOCK#前缀指令,此时当前处理器的缓存行就会被锁定,然后更新对应的主存地址的数据。
2.在写回时,CPU嗅探机制会察觉到数据的修改然后使其他线程中的缓存数据失效,当需要用到该数据时再去内存中读取,保证读取到的数据是修改后的值,保证了数据缓存的一致性。

与总线加锁不同的是这种方式只在数据写回主内存时加锁,不需要等待其程序执行的时间,而数据写回的速度非常快,几乎对性能没有影响,保证了线程间的并行执行。

volatile会保证数据的可见性和有序性,但不能保证原子性。

你可能感兴趣的:(多线程与并发)