volatile 关键字的作用与原理
线程的原子性、可见性、有序性
原子性
原子性是指在一个操作中就是 cpu 不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
如果一个操作是原子性的,那么多线程并发的情况下,就不会出现变量被修改的情况。
在 Java 中,除了 long 和 double 类型,其他基本数据类型的变量均具有原子性。
可见性
可见性是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。
有序性
有序性是指程序在执行过程中的先后顺序,由于 Java 在编译器以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。
影响有序性的主要原因是指令重排序。
指令重排序是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致。如以下语句,在实际的代码执行过程中,可能出现语句2先与语句1执行的情况。
int i = 0;
boolean flag = false;
i = 1; // 语句1
flag = true; // 语句2
内存屏障
内存屏障是对一类仅针对内存读写操作指令的跨处理器架构的比较底层的抽象。
内存屏障是被插入到两个指令之间进行使用的。其作用是禁止编译器、处理器重排序而保障有序性。
但是为了实现禁止重排序的功能,这些指令往往有一个副作用——刷新处理器缓存、冲刷处理器缓存,从而保障有序性。
针对于可见性,又分为加载屏障和存储屏障。
针对于有序性,又分为获取屏障和释放屏障。
加载屏障(读屏障)
加载屏障的作用是刷新处理器缓存。
Java 虚拟机会在 MonitorEnter(申请锁)的机器指令码之后,临界区开始之前的地方插入一个加载屏障,这使得读线程的执行处理器能够将写线程对相应共享变量所做的更新从其他处理器同步到该处理器的高速缓存中。
存储屏障(写屏障)
存储屏障的作用是冲刷处理器缓存。
Java 虚拟机会在 MonitorExit(释放锁)的机器指令码之后插入一个存储屏障,这就保证了写线程在释放锁之前在临界区中对共享变量所做的更改,对读线程的执行处理器来说是可同步的。
获取屏障(读屏障)
获取屏障的使用方式是在一个读操作之后插入该内存屏障。其作用是禁止该读操作与其后的任何读写操作进行重排序。
释放屏障(写屏障)
释放屏障的使用方式是在一个写操作之前插入该内存屏障。其作用是禁止该写操作与其前面的任何读写操作之间进行重排序。
volatile 关键字的作用与原理
保障原子性
volatile 关键字能保障 long 和 double 类型变量的写操作具有原子性。(其他类型的基本数据类型本身都具有原子性)。
注意:不能保障 volatile 的赋值操作具有原子性。如下面的 num++ 操作的原子性是不可保证的。
volatile int num = 0;
num++;
保障可见性
对 volatile 变量的写操作,java 虚拟机会在该操作之前插入一个写屏障。该写屏障保证了在 volatile 变量的写操作 之前的读写操作对共享变量所做的更改都能同步到主存当中。即其他线程看到写线程对volatile变量的更新时,写线程在更新volatile变量之前所执行的内存操作的结果对于读线程必然也是可见的。
public void test() {
i = 1;
// flag 是 volatile 变量
flag = true;
// 这里会插入一个写屏障
// i = 1 这个写操作的结果会被同步到主存中
}
对 volatile 变量的读操作,java 虚拟机会在该操作之后插入一个读屏障。该读屏障保证了在 volatile 变量读操作 之后加载的都是主存中的最新数据。
public void test() {
// flag是 volatile 变量
if (flag) {
// 这里会插入一个读屏障
// 保障了 i 是主存中最新的值
j = i;
}
}
保障有序性
对 volatile 变量的写操作。java 虚拟机会在该操作之前插入一个写屏障。该屏障禁止了 volatile 变量的写操作与其前面的任何读写操作之间进行重排序。
public void test() {
i = 1;
// flag 是 volatile 变量
// 这里会插入一个写屏障
// 前面的读写操作不会指令重排序到 flag = true 的后面
flag = true;
}
对 volatile 变量的读操作。java 虚拟机会在该操作之后插入一个读屏障。该屏障禁止了 volatile 变量 的读操作与其后的任何读写操作进行重排序。
public void test() {
// flag是 volatile 变量
if (flag) {
// 这里会插入一个读屏障
// flag 后面的操作不会指令重排序到它的前面
j = i;
}
}