Volatile关键字
一、Volatile的用法
Volatile是java虚拟机提供的轻量级的同步(锁)机制。它可以保证共享变量的可见性以及禁止指令重排,但是不保证原子性。
二、理解可见性、禁止指令重排、不保证原子性
1、可见性:意思是当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值。这得认识ava内存模型(简称JMM),在JVM运行时,每个线程会开辟工作内存空间,工作内存是线程私有的数据区域。而java内存模型中规定所有的变量都存储在主内存,线程对变量的操作必须在工作内存中进行,因此首先将变量从主内存拷贝到自己的工作内存(即每个线程存储着主内存的变量副本拷贝),操作完成后再将变量写回主内存。并且保证第一时间让通知其他线程可访问主内存,这种机制就叫可见性。
2、禁止指令重排:计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为3种:原代码-->编译器优化的重排-->指令并行的重排-->内存系统的重排--> 最终执行的指令
在单线程环境里面的执行结果和代码顺手的执行结果是一致的,处理器在进行重排序时必须考虑指令之间的数据依赖性。但是在多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的。结果无法预测!
那什么是数据的依赖性?看如下代码:
按正常的代码正常执行顺序是1234,指令重排后的执行顺序可能是2134、1324,但是不可能4在第一执行,因为它的执行依赖与X的初始化。
所以Volatile禁止指令重排可以避免多线程环境下程序出现乱序执行的情况,从而保证变量的线程安全(某些场景)。
3、不保证原子性:原子性及保证数据的一致性和完整性,意味着某一组操作"不可分割",在多线程并发情况下,指线程不会被线程调度器中断、交叉运行。
如图:3个线程对共享变量i执行i++操作,如果线程1和线程2同时读取到i=0,然后都执行i++;当1号线程准备写回数据到主内存,此时若线程1被挂起,线程2先写回主内存的数据i=1;但是线程1这个时候再写回主内存时,也为1并覆盖线程线程2的值,线程3读到的数据并不是预期值2,而是1,这就造成数据丢失,产生线程安全问题。
三、Volatile的实现原理
由于编译器和处理器都能执行指令重排优化。如果指令间插入一条内存屏障则会告诉编译器和CPU,不管什么指令都不能和这条内存屏障指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。
内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
对Volatile变量进行写操作时,会在写操作后加入一条store屏障指令,讲工作内存中的共享变量值刷新回主内存。
对Volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。