站点图标 度崩网-几度崩溃

volatile关键字的作用是什么?

Java 中volatile 关键字是一个类型修饰符。JDK 1.5 之后,对其语义进行了增强。

volatile 可见性的实现

看一下我们之前的一个可见性问题的测试例子

package constxiong.concurrency.a014;/** * 测试可见性问题 * @author ConstXiong */public class TestVisibility {//是否停止 变量private static boolean stop = false;public static void main(String[] args) throws InterruptedException {//启动线程 1,当 stop 为 true,结束循环new Thread(() -> {System.out.println("线程 1 正在运行...");while (!stop) ;System.out.println("线程 1 终止");}).start();//休眠 10 毫秒Thread.sleep(10);//启动线程 2, 设置 stop = truenew Thread(() -> {System.out.println("线程 2 正在运行...");stop = true;System.out.println("设置 stop 变量为 true.");}).start();}}

程序会一直循环运行下去

这个就是因为 CPU 缓存导致的可见性导致的问题。

线程 2 设置 stop 变量为 true,线程 1 在 CPU 1上执行,读取的 CPU 1 缓存中的 stop 变量仍然为 false,线程 1 一直在循环执行。

示意如图:

给 stop 变量加上 valatile 关键字修饰就可以解决这个问题。

volatile 有序性的实现

1) 对一个 volatile 变量的写 happens-before 任意后续对这个 volatile 变量的读

2) 在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作

3) happens-before 传递性,A happens-before B,B happens-before C,则 A happens-before C

1) 在程序运行时,为了提高执行性能,在不改变正确语义的前提下,编译器和 CPU 会对指令序列进行重排序。

2) Java 编译器会在生成指令时,为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的指令重排序

3) 编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令

4) 内存屏障会告诉编译器和 CPU:不管什么指令都不能和这条 Memory Barrier 指令重排序

内存屏障

1) 在每个 volatile 写操作的前面插入一个 StoreStore 屏障
2) 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障
3)在每个 volatile 读操作的后面插入一个 LoadLoad 屏障
4)在每个 volatile 读操作的后面插入一个 LoadStore 屏障

1) StoreStore:禁止之前的普通写和之后的 volatile 写重排序;
2) StoreLoad:禁止之前的 volatile 写与之后的 volatile 读/写重排序
3) LoadLoad:禁止之后所有的普通读操作和之前的 volatile 读重排序
4) LoadStore:禁止之后所有的普通写操作和之前的 volatile 读重排序

我觉得,有序性最经典的例子就是 JDK 并发包中的显式锁java.util.concurrent.locks.Lock 的实现类对有序性的保障。

以下摘自:http://ifeve.com/java锁是如何保证数据可见性的/

实现 Lock 的代码思路简化为

private volatile int state;void lock() {read stateif (can get lock)write state}void unlock() {write state}

Happens-before 规则:一个 volatile 变量的写操作发生在这个 volatile 变量随后的读操作之前。