volatile 是什么
volatile 是 java语言的一个关键字,用于声明变量,也被称为轻量级的“synchronized”,意味着以更低的代价实现线程同步。因其只能保证可见、有序性,但不能保证原子性,所以只能解决特定并发场景下线程安全的一种手段。
示例
1. 保证可见性
以下这段代码有没有问题呢?子线程是否停止?
public class VisibilityProblem {
private static boolean flag = true; // 没有volatile
public static void main(String[] args) throws InterruptedException {
Thread workerThread = new Thread(() -> {
System.out.println("工作线程开始执行...");
while (flag) {
// 空循环,等待flag变成false
}
/**可能不会执行到这*/
System.out.println("工作线程检测到flag变化,退出循环");
});
workerThread.start();
// 主线程休眠1秒,确保工作线程已经开始运行
Thread.sleep(1000);
System.out.println("主线程修改flag为false");
flag = false;
workerThread.join();
System.out.println("程序结束");
}
}
- 这段代码定义了一个flag变量,主线程休眠一段时间后,将flag变量修改为false,子线程判断变量变成false后,就停止运行。
- 实际上子线程可能永远不会停止运行,因每个线程都有自己的工作内存(可以理解为cpu的高速缓存),如有A、B 两个线程,每个线程都复制了flag变量到各自的工作内存中,变量的读取修改操作的都是各自的工作内存的数据,数据的变化,线程间是没有任何变化感知的,这就造成了数据可见性问题。
- 可见性简单来说,就是能保证实时访问到最新的数据,这段代码,只有给 变量flag,加上volatile修饰,程序就正常结束了。
加上volatile修饰
public class VisibilityProblem {
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread workerThread = new Thread(() -> {
System.out.println("工作线程开始执行...");
while (flag) {
// 空循环,等待flag变成false
}
/**可能不会执行到这*/
System.out.println("工作线程检测到flag变化,退出循环");
});
workerThread.start();
// 主线程休眠1秒,确保工作线程已经开始运行
Thread.sleep(1000);
System.out.println("主线程修改flag为false");
flag = false;
workerThread.join();
System.out.println("程序结束");
}
}
原理
- 这个是硬件工程师给软件工程师埋的坑,我们知道,cpu的运算速度是很快的,但运算需要数据的支持,数据来源于内存,内存的访问速度是很慢的,根据木桶效应,能装多少水取决于最短的那块板,但不能因为内存访问慢,就限制cpu的发展啊!所以硬件工程师们,就给cpu增加了高速缓存,正是这个高速缓存,导致了可见性的问题发生。
- 对于类似问题,通过volatile关键字,可以禁用缓存,当然,不是完全禁用,是有选择、按需的禁用。volatile关键字还可以禁止指令重排。
volatile 实现原理主要依赖于处理器的内存屏障及java内存模型
- 当volatile变量修改前,会发一条StoreStore屏障指令给处理器,确保这个变量的写操作都刷新到主存上。
- 执行修改。
- 之后,发送一条StoreLoad屏障指令,强制将cpu高速缓存写的数据刷新到主存上,并使这个变量在其他处理器高速缓存中的内存地址失效掉。
读操作
总结
- volatile 关键字的作用是通过内存屏障的方式保证线程的可见性及有序性。
- 一般适用于对单个变量操作,如设定标志位,等标志位发生变化时,触发其他操作等类似场景判断。
如果今天只能记住一句话,那就是:volatile是一个关键字,可以按需禁用高速缓存来实现线程同步。