目录
简而言之
专业术语解释
1、可见性
原理简介
原理图解
其他方式
2、原子性
原理简介
结合实例分析
3、有序性
原理简介
线程安全问题
Volatile效果
1、保证可见性
2、保证有序性
3、无法保证原子性
Volatile底层的实现机制(重点掌握)
经典案例
Java双重检验锁的单例模式
简而言之
volatile修饰的共享变量特性:
- 保证多线程对该变量操作的内存可见性
- 禁止指令重排序
专业术语解释
1、可见性
原理简介
Java虚拟机(JVM)定义Java内存模型(JMM)来保证JAVA程序在所有平台具有相同内存访问效果
Java内存模型(JMM)规定所有变量存在主存中,各线程只能通过各自的工作内存访问、获取
出现问题:多线程执行i++时,可能线程A获取i为1,线程B获取i为2,因为缓存不一致!!!
当用volatile修饰,各线程对它修改会立刻刷新到主存,各线程都能读取到最新数据,即可见性
原理图解
其他方式
- synchronized
- Lock
线程释放锁之前,会将共享变量值(被上面两个修饰的变量)刷新到主存
2、原子性
原理简介
基本数据类型的读取和赋值操作 不可中断 ,要么不执行,要么执行完
结合实例分析
i = 2;
j = i;
i++;
i = i + 1;
i = 2 ,为原子性操作。属于基本数据类型int型赋值语句
j = i 不符合原子性。分为读取 i 的值和为 j 赋值两步
i++和i = i+1 等效,都不符合原子性。分为三步,先读取 i 的值,再加1,最后 i 值写回主存
3、有序性
原理简介
Java内存模型(JMM)允许编译器和处理器在不影响程序执行结果前提下可以重排序语句
var pi = 3.14 //A
var r = 1 //B
var s= pi*r*r //C
比如正常从上至下原则是 A->B->C
编译后可以是 B->A->C,因为AB相互不依赖
但是C不可以倒反天罡在AB二者之前执行
线程安全问题
var a = 0
var flag = false
fun write() {
a = 2;
flag = true
}
fun multiply() {
if (flag) {
val ret:int = a * a
}
}
解读:这里线程1执行write方法,write方法两个指令重排序了,线程2执行multiply方法。
由于重排序,write没执行完,先执行了Flag语句,结果线程2开始执行,运行结果也错误
此时可以为共享变量flag加volatile修饰,禁止重排序!!!
Volatile效果
var a = 0
volatile var flag = false
fun write() {
a = 2;
flag = true
}
fun multiply() {
if (flag) {
val ret:int = a * a
}
}
假设线程1调用write(),线程2调用multiply()由于flag在两个方法都有依赖,属于共享变量
1、保证可见性
当一个变量被声明为 volatile
时,
线程在每次使用变量的时候都会直接从主内存中读取,而不是从自己的工作内存中读取;=。
volatile确保在线程1中flag值更新立刻刷新主存,线程2也从主存随时读取最新flag
2、保证有序性
通过一种同步机制——内存屏障,阻止屏障两侧指令被重排序,确保变量的读写顺序按照代码逻辑顺序,维持多线程环境中操作的有序性。
3、无法保证原子性
只是对单个volatile变量的读/写具有原子性
但是,volatile
关键字的复合操作(如自增、自减等操作)无原子性。
在多线程环境中,i++,i--无原子性
import kotlin.concurrent.thread
class VolatileExample {
@Volatile
private var count = 0
fun increment() {
count++ // 这个操作在Kotlin中也不是原子性的
}
fun getCount(): Int {
return count
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
val example = VolatileExample()
val t1 = thread {
repeat(1000) {
example.increment()
}
}
val t2 = thread {
repeat(1000) {
example.increment()
}
}
t1.join()
t2.join()
println("Final count is: ${example.getCount()}")
}
}
}
Volatile底层的实现机制(重点掌握)
- 生成汇编代码后,加入的volatile关键字会多出lock前缀
- lock相当于内存屏障
- 屏障作用是重排序是,阻止后面指令重排序到屏障之前
经典案例
Java双重检验锁的单例模式
class Singleton{
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null)
instance = new Singleton();
}
}
return instance;
}
}