多线程之volatile
多线程之volatile
线程同步另一个重要方面是内存可见性, 加锁不仅保证互斥同样保证可见。
JMM
Java内存模型简称JMM(Java Memory Model
- 主内存: 主内存被所有的线程所共享,对于一个共享变量(比如静态变量,或是堆内存中的实例)来说,主内存当中存储了它的“本尊”。
- 工作内存: 简单理解为计算机当中的CPU高速缓存,但又不完全等同。每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存当中存储了它的“副本”。
指令重排
指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。但是在某些情况下,会影响到多线程的执行结果
eg:
- 有可能readerThread 看不到ready为true, 一直循环
- 也有可能看不到number = 42, 输出0

Volatile
volatile 是比synchronized 关键字更轻量级的同步机制。
保证可见
对一个volatile变量的读,(任意线程)总是能看到对这个volatile变量最后的写入。
- 一个线程修改volatile变量的值时,该变量的新值会立即刷新到主内存中,这个新值对其他线程来说是立即可见的。
- 一个线程读取volatile变量的值时,该变量在本地内存中缓存无效,需要到主内存中读取。
保证有序
禁止重排序规则
boolean inited = false;// 初始化完成标志 |
期望行为: 线程1初始化配置,初始化完成,设置inited=true。线程2每隔1s检查是否完成初始化,初始化完成之后执行doSomething方法。
潜在问题: 线程1中,语句1和语句2之间不存在数据依赖关系,JMM允许这种重排序。例如在**程序执行过程中发生重排序,先执行语句2后执行语句1: 线程1 先执行语句2, 配置并未加载,而线程2 读取到inited=true,开始执行后续操作。。。
volatile修饰inited,这样不会重排
典型用法: 检查某个标记以判断是否退出循环。
volatile boolen asleep;
...
while (!asleep) {
countSomeSheep();
}
public class VolatileTest { |
a++ 并不是原子操作 (读取-修改-写入)
解决:
- synchronized
- CAS - AtomicInteger
(Optional). Volatile 原理
java编译器在生成字节码时,在volatile变量操作前后的指令序列中插入内存屏障来禁止特定类型的重排序。
总结
并发编程中,常用volatile修饰变量以保证变量的修改对其他线程可见。
volatile可以保证可见性和有序性,不能保证原子性。
volatile是通过插入内存屏障禁止重排序来保证可见性和有序性的。
参考