volatile的作用
首先,volatile
关键字只能修饰变量,不能修饰方法或者代码块
volatile
有两个作用:
- 保证线程的可见性:一个线程对
volatile
变量的修改,会立刻反映到其他线程中,其他线程能够立刻看到这个变量的最新值,避免出现多线程中变量数据不一致的问题 - 禁止指令重排序:
volatile
变量的写操作之前的所有操作,即使重排序,会被重排序到写操作之前;而写操作之后的所有操作,即使重排序,也只会重排序到写操作之后。保证了多线程操作的逻辑正确性
为什么会指令重排序?
白话就是“实际执行的代码顺序和写的代码顺序不一样”
原因:指令重排序是由编译器、处理器(CPU)或多线程调度器在优化程序执行效率时进行的一种操作,改变了代码中的某些指令的执行顺序(但不会更改代码的逻辑顺序)。
特点:
- 单线程中,即使指令重排序,因为不会改变逻辑顺序(依赖关系),所以不会对结果产生影响,只会提升效率
- 多线程中,由于在不同线程之间存在共享资源,指令重排序改变了程序中的某些操作的执行顺序,可能导致不同线程之间的操作不按预期顺序进行的问题。
volatile如何禁止指令重排序?
更严格地说,应该叫做“volatile禁止指定变量参与指令重排序”
原理:volatile通过内存屏障来禁止指令重排序
JVM内存屏障地策略
- 在每个
volatile
写操作的前面插入一个StoreStore屏障。 - 在每个
volatile
写操作的后面插入一个StoreLoad屏障。 - 在每个
volatile
读操作的前面插入一个LoadLoad屏障。 - 在每个
volatile
读操作的后面插入一个LoadStore屏障。
这个原理流程图我还没琢磨透,但是简而言之,按照如下意思理解就可以了:
原本代码块都会被指令重排,但是如果一个变量被volatile
修饰了,在编译和处理器阶段(即发生指令重排序的时候),将会分为三部分:
- ①
volatile
之前的代码 - ②
volatile
修饰的变量的代码 - ③
volatile
之后的代码; - 其中①和③分别进行指令重排序(前面的代码不会重拍到后面,后面的代码不会重排到前面),这样就避免了重排序导致的多线程逻辑错误