volatile底层原理(并发编程篇)
目录
问题:10个线程分别对线程执行1000次i++操作,结果一定是10000吗?
学习volatile之前需要掌握
工作空间与主内存之间的交互
java内存中线程的工作内存和主内存的交互是由java虚拟机定义了如下的8种操作来完成的,每种操作必须是原子性的。
① lock(锁定):作用于主内存的变量,一个变量在同一时间只能一个线程锁定,该操作表示这条线成独占这个变量;
② unlock(解锁):作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定;
③ read(读取):作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用;
④ load(载入):作用于线程的工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中
(副本是相对于主内存的变量而言的);
⑤ use(使用):作用于线程的工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个
需要使用变量的值的字节码指令时就会执行该操作;
⑥ assign(赋值):作用于线程的工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到
一个给变量赋值的字节码指令时就会执行该操作;
⑦ store(存储):作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用;
⑧ write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。
volatile的定义
Java语言规范第三版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了Volatile,在某些情况比synchronized更方便。如果一个变量被声明为volatile的,java内存模型确保所有线程看到这个变量的值是一致的。
Volatile关键字的语义分析
①:保证共享变量的可见性
public class LazySingleton {
private static volatile LazySingleton instance = null;
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
public static void main(String[] args) {
LazySingleton.getInstance();
}
我们将代码转换为汇编指令,在汇编指令的中我们可以找到如下信息
0x0000000002931351: lock add dword ptr [rsp],0h ;*putstatic instance
; - org.xrq.test.design.singleton.LazySingleton::aaa@qq.com (line 14)
volatile修改的共享变量,会多出一行汇编指令Lock,lock在多核处理器下会做2件事;
第1:将当前处理器的缓存行数据写回主内存;
第2:使其他CPU缓存了该变量内存地址的数据无效,当其他CPU重新从内存里面去读取该变量;
②:保证共享变量的有序性;
当对volatile修饰的共享变量读写时,java编译器会在生成指令序号的适当位置插入内存屏障指令来禁止特定类型的处理器重排序。
JMM基于保守策略插入内存屏障规则如下:
1.在每个volatile写操作的前面插入一个StoreStore屏障。
2.在每个volatile写操作的后面插入一个StoreLoad屏障。
3.在每个volatile读操作的前面插入一个LoadLoad屏障。
4.在每个volatile读操作的后面插入一个LoadStore屏障。
问题:10个线程分别对线程执行1000次i++操作,结果一定是10000吗?
public class VolatileDemo {
private volatile static int count = 0;
private static void add() {
count++;
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
add();
}
}
});
threads[i].start();
}
Thread.sleep(3000);
System.out.println("count:" + count);
//结果:count<= 10000
//结论:volatile 不能保证原子性
}
}
分析:初始值主内存为0,单线程1、2同时读取0到自己的工作空间时,线程1进行+1操作结果count=1 然后正准备执行Load原子操作将数结果1写入主内存时,此刻线程2执行+1操作完成结果为1,线程1将结果写入主内存经过总线触发总线嗅探机制使线程工作空间的缓存无效,但是此刻线程2已经计算完成结果为1正准备写入主内存,所有此刻是线程2的缓存无效,对线程2的结果没有任何影响,线程2将结果写入主内存,最终主内存的结果还是为1。
总结:volatile能保证共享变量的可见性和有效性但是不能保证原子性。。。
volatile的使用场景
① 状态标志(开关模式)
private volatile boolean started=false;
@Override
public void run() {
while(started){
dowork();
}
}
public void shutdown(){
started=false;
}
② 双重检查锁定(double-checked-locking)
private volatile static Singleton instance;
public static A getInstance(){
if(instance==null){
synchronized (A.class){
instance=new A();
}
}
return instance;
}
③ volatile最适合使用的地方是一个线程写、其它线程读的场合
volatile和synchronized的区别
① 使用上的区别
Volatile只能修饰变量,synchronized只能修饰方法和语句块;
② 对原子性的保证
synchronized可以保证原子性,Volatile不能保证原子性;
③ 对可见性的保证
都可以保证可见性,但实现原理不同
Volatile对变量加了lock,synchronized使用monitorEnter和monitorexit monitor JVM;
④ 对有序性的保证
Volatile能保证有序,synchronized可以保证有序性,但是代价(重量级)并发退化到串行;
⑤ 其他
synchronized引起阻塞,Volatile不会引起阻塞。