目录
一、简介
二、JMM与CPU
三、Volatile的语句分析
四、Volatile使用场景
五、volatile与synchronized的区别
synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁。而volatile就可以说是java虚拟机提供的最轻量级的同步机制。但它同时不容易被正确理解,也至于在并发编程中很多程序员遇到线程安全的问题就会使用synchronized。
被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。当然这是在多线程的情况下,单线程没啥意义。
volatile也能解决数据不一致问题,主要表现在内存层级上,大家继续往下看。
我们在第一节JMM中介绍过CPU的缓存模型,我们先回顾下
如下图所示,因为每个cpu有自己独立的寄存器和缓存,但是内存是共享的,所以会存在内存不一致性问题。
大部分一致性解决方案就是:
1、总线枷锁 (粒度太大)
2、MESI 缓存一致性协议,基于cache line 实现
a. 读操作:不做任何事情,把Cache中的数据读到寄存器
b. 写操作:发出信号通知其他的CPU将该变量的Cache line置为无效,其他的CPU要访问这个变量的时候,只能从内存中获取。
JAVA内存模型描述:
1) 主存中的数据所有线程都可以访问(共享数据)
2) 每个线程都有自己的工作空间,(本地内存)(私有数据)
3) 工作空间数据:局部变量、内存的副本
4) 线程不能直接修改内存中的数据,只能读到工作空间来修改,修改完成后刷新到内存
这个模型结构大家现在是不是已经深入刻在脑子里面了?
使用了Volatile修饰的变量,被一个线程修改了以后,另外一个线程在读取到这个变量的时候就是最新的值,所以才会成为可见性,Volatile可以理解成为就是在JMM上实现了类似MESI协议。MESI在硬件层级上是使cache line失效。Volatile就是在JMM上实现变量修改后,其他线程马上感知到。怎么感知的呢?cpu的总线会将信息传递给各个线程。
Volatile的作用: 就是让其他的线程能够马上感知到某一线程对某个变量的修改。
volatile主要保证2个特性,可见性(多个现场见自己的工作空间里面的数据保持一致)和有序性。不保证原子性。
1、保证可见性:
对共享变量的修改,其他的线程马上能感知到;
不能保证原子性,如果N个线程都从内存取出值到工作内存,修改后,又一起写回去。就会出现多次覆盖。因为失效是工作空间里面的失效了。但是如果都已经处于写回状态时,就没办法了,会多次一样的值覆盖。
2、保证有序性:
编辑器优化和指令重排。
重排序(编译阶段、指令优化阶段);
输入程序的代码顺序并不是实际执行的顺序;
重排序后对单线程没有影响,对多线程有影响;
对于Volatile修饰的变量 在重排序的时候有如下规则:
1)volatile之前的代码不能调整到它后面
2)volatile之后的代码不能调整到它之前
3)volatile修饰的代码位置不变
例如:
Int i=0;
Int a=3;
Int b=5;
Volatile Int j=3;
顺序重排后不能出现这样的情况。
Volatile Int j=3;
Int i=0;
Int a=3;
Int b=5;
volatile的实现就是加了一个锁:LOCK。
通过javap 命令,将字节码文件反编译。观察反编译的结果,对于volatile修饰的变量,发现反编译得到的字节码并没有什么帮助,和不加volatile修饰的变量没有任何区别。也就是说,字节码层面volatile变量并没有什么不同。
下面通过查看Java的汇编指令,查看Java代码最真实的运行细节。
大家可以在网上找个hsdis-amd64.dll 然后放到jdk文件夹下面/jre/bin/server中,然后再执行的时候加上:-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
执行就可以看到字节码了。这个lock cpmxchg 就好比是一个栅栏一样,使得上面的指令无法到下面去,下面的指令无法到上面去,达到了有序性的目的。
1、状态标志(开关模式)
代码如下:
public class ShutDowsnDemmo extends Thread{
private volatile boolean started=false;
@Override
public void run() {
while(started){
dowork();
}
}
public void shutdown(){
started=false;
}
}
2、双重检查锁(double-checked-locking)
常见到的是单例模式,下节我们在主要介绍单例模式。
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
instance=new Singleton();
}
}
return instance;
}
}
3、需要利用顺序性(防止被指令重排)
public class VolaitleDemo1 {
private static volatile int m=0;
public static void main(String[] args) {
int i=0,j=0;
i++;
m++;
j=i+m;
System.out.println("last value : "+j);
}
}
1、使用上的区别
Volatile只能修饰变量;
synchronized只能修饰方法和语句块;
2、对原子性的保证
synchronized可以保证原子性;
Volatile不能保证原子性;所以不要用来做值增加等操作,当开关还是可以的。
3、对可见性的保证
都可以保证可见性,但实现原理不同
Volatile对变量加了lock;
synchronized使用monitor 的 monitorEnter和monitorexit 或者 信号量
4、对有序性的保证
Volatile能保证有序;
synchronized可以保证有序性,但是代价太大(重量级锁)并发退化到串行,所以不要在代码块里面写太多的业务代码。
5、阻塞
synchronized引起阻塞
Volatile不会引起阻塞