1. volatile变量 两大特性
可见性、有序性(排序要求:禁止重排)
写,线程本地内存中的共享变量值立即刷新回主内存
读,线程本地内存设置为无效,重新回到主内存中读取最新共享变量
Q:volatile凭什么可以保证可见性和有序性?
--- 内存屏障 Memory Barrier
2. 四大内存屏障(面试重点)
“人墙”
内存屏障是什么?
写后读
内存屏障分类:
粗分2种
读屏障(Load Barrier)
写屏障(Store Barrier)
细分4种
Unsafe.class -> Unsafe.java -> Unsafe.cpp -> OrderAccess.hpp -> orderAccess_linux_x86.inline.hpp
3. Volatile读写屏障插入策略
什么叫保证有序性?--禁重排 --通过内存屏障禁重排
1)volatile读
2)volatile写
4. volatile可见性案例
package com.bilibili.volatiles;
import java.util.concurrent.TimeUnit;
/**
* volatile 可见性
*
* @author baiyuehua
*
*/
public class VolatileSeeDemo {
// static boolean flag = true; // 程序无法停止
static volatile boolean flag = true; // 程序可以停止
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t---come in");
while (flag) {
}
System.out.println(Thread.currentThread().getName()+"\t---flag被设置为false,程序停止");
}, "t1").start();;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println(Thread.currentThread().getName()+"\t---修改完成flag: "+flag);
}
}
volatile变量的读写过程
5. volatile无原子性案例
volatile变量的复合操作不具有原子性。eg:number++
不保证原子性,写丢失问题
结论:volatile变量不适合参与到依赖当前值的运算 ---《深入理解Java虚拟机》
面试回答: JVM的字节码,i++分为三步,间隙期不同步非原子操作。
对于volatile变量,JVM只是保证从主内存加载到线程工作内存的值是最新的,也只是数据加载时时最新的。如果第二个线程在第一个线程读取旧值和写回新值期间读取 i 的域值,也就造成了线程安全问题。
6. volatile禁重排案例
- 不存在数据依赖关系,可以重排序;
- 存在数据依赖关系,不可以重排序。
1)重排序的分类和执行流程:
2)数据依赖性:
若两个操作访问同一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性。
3)重排序,程序执行结果改变,三种情况:
7. volatile适用场景
1)单一赋值可以,but含复合运算赋值不可以(i++之类)
2)状态标志,判断业务是否结束
3)开销较低的读,写锁策略
4)DCL双端锁的发布
高并发下的单例模式
问题:
单线程看问题:
多线程看问题:
解决:
加volatile修饰
8. volatile小总结
1)可见性
2)非原子性
3)禁重排
写指令
读指令
Q:凭什么我们java写了一个volatile关键字,系统底层就加入内存屏障?两者是怎么关联上的?
Q:内存屏障是什么?
Q:内存屏障能干嘛?
内存屏障的四大指令:
3句话总结: