volatile的作用
volatile主要用于解决可见性和有序性的问题,但不保证原子性
- 可见性:
- 线程在操作变量时,会将主存中的变量拷贝一份到本地存储;修改有再找时机写回主存(不可控),这样多线程并发时会导致其他线程看到的数据和当前线程不一致
- 使用
volatile
关键字修饰变量,可使得每次执行写操作时直接将值刷新到主存中,通过内存屏障使得其他线程本地副本失效;执行读操作时会强制从主存中加载最新的值。
- 禁止指令重排序
- JVM和CPU可能会对指令进行重排序优化(单例模式中如果静态变量不使用
volatile
关键字修饰会有重排序的风险) volatile
修饰的变量通过插入内存屏障来禁止编译器和处理器对指令进行重排序,来保证执行符合预期
- JVM和CPU可能会对指令进行重排序优化(单例模式中如果静态变量不使用
适用场景
- 状态标志位:如 volatile boolean flag,用于简单线程间通信。
- 【设计模式】单例模式 的双重检查锁(Double-Checked Locking)。
- 多线程环境下仅由一个线程写、其他线程读的变量,例如:【多线程】CopyOnWriteList。
volatile的写操作和读操作都是直接操作主存吗?
写操作:
执行写操作时,使用内存屏障将写入主存的变量在其他线程的副本状态置为失效,然后强制其他线程再要使用该变量时从主存中获取。
读操作:
读操作分两种情况
-
线程在对
volatile
变量进行读取时,如果本地副本状态正常,则从本地缓存中读取该变量的值
-
线程在对
volatile
变量进行读取时,如果本地副本状态时失效,则从主存中读取该变量的值,刷新到本地缓存
与synchronized的区别
synchronized
用于实现原子性和互斥访问,同时隐含了可见性和有序性- 原子性:
synchronized
关键字通过锁机制保证同一时间只有一个线程执行临界区代码,保证原子性 - 可见性和有序性:线程进入
synchronized
代码块时会从主存加载变量,写入本地内存;退出时会将本地内存刷新到主内存中;通过锁来保证代码块的代码不会被重排序到外部
- 原子性:
- 两者最大的区别
volatile
修饰的变量所有的线程在变量发生变更后都会从主内存去取。
synchronized
代码块修改的变量(里面多线程共享的变量未被volatile
修饰),只有进入synchronized
代码的时候才会从主内存中取。
- 所以多线程编程时如果对于某个变量是多线程的读和写,一定要加上
volatile
关键字修饰,否则未加锁的读线程可能会读到旧值