看完这篇volatile volatile什么用

推荐学习

  • 死磕「并发编程」100天,全靠阿里大牛的这份最全「高并发套餐」
  • Aliyun四面真可怕,狂问基础+项目+源码+高阶,无爱了
前言volatile关键字相信大家多多少少都见过,即使没实际用过,也在诸多源码里见过 。本文主要围绕volatile是什么、实现原理、使用场景几方面展开介绍 。
在介绍volatile之前,应该先了解下Java内存模型(JMM,Java Memory Model)
看完这篇volatile volatile什么用

文章插图
从图中我们可以看到JMM实际上是一种线程通信的规范,具体实现是通过共享内存(工作内存是线程隔离的,主内存是共享的) 。线程不能直接修改主内存中的变量,线程1和线程2中的变量都是从主内存中都取,在工作内存中修改后更新到主内存,工作内存中存储的是主内存中变量的副本 。
JMM与JVM内存的各大区域是不同层次的划分 。主内存中主要存储Java对象的实例,包括成员变量、类信息、常量、静态变量 。由于是线程共享的,所以存在线程安全问题;工作内存中主要存储方法的本地变量信息、字节码信号指示器等,由于是线程隔离的,所以不存在线程安全问题 。
为什么需要JMM学过计算机基础的同学应该都知道,CPU寄存器的存取速度和内存(Memory)的速度相差了好几个数量级 。如果CPU直接操作内存(Memory),那么CPU绝大多数时间都处于等待状态,会导致CPU利用率极低 。所以不得不在CPU和内存(Memory)之间增加高速缓存(一般是三级缓存L1、L2、L3 。L1和L2是隔离的,L3是共享的) 。具体结构如下
看完这篇volatile volatile什么用

文章插图
有了缓存层,操作系统可以提前将CPU需要使用到的数据读入缓存,计算完成后写回缓存,再由缓存慢慢同步给内存(Memory) 。这样提高了CPU利用率,但是也增加了系统的复杂度,那就是引入了数据一致性问题 。存在多个CPU的时候,怎么保证各个CPU缓存中数据是一致的 。为了保证数据性一致,需要在访问缓存的同时,遵循一些协议,如MESI 。
三级缓存结构虽然解决了CPU和内存的速度差异问题,但是三级缓存的结构过于复杂,并且不同的CPU结构略有差异 。Java作为一门跨平台,跨操作系统的语言,就需要一种规范来屏蔽各类厂家CPU以及各个操作系统之间的差异,这种规范正是Java内存模型(JMM) 。
很显然,JMM的这种设计也会存在数据一致性问题 。导致数据不一致的原因主要有三个:原子性、可见性、有序性 。
  • 原子性:一系列操作不可分割,要么都成功执行,要么都失败 。
  • 可见性:一个线程对共享变量的修改能及时被其他线程知晓 。
  • 有序性:为了提高程序的执行效率,编译器会对编译后的指令进行重排序,即代码的编写顺序不一定就是代码的执行顺序 。
重排序又可以分为三种:
  • 编译器优化的重排序 。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
  • 指令级并行的重排序 。现代CPU采用了指令级并行技术来将多条指令重叠执行 。对于不存在数据依赖的指令,CPU可以改变语句对应机器指令的执行顺序
  • 内存系统的重排序 。由于CPU使用三级缓存结构,这使得数据加载和存储操作看上去可能是在乱序执行的
以下代码可以验证指令重排序
/** * @author sicimike */public class OutOfOrder {private static int x = 0, y = 0;private static int a = 0, b = 0;public static void main(String[] args) throws InterruptedException {int i = 0;for (; ; ) {i++;x = 0; y = 0;a = 0; b = 0;Thread one = new Thread(() -> {a = 1;// 1x = b;// 2});Thread two = new Thread(() -> {b = 1;// 3y = a;// 4});one.start(); two.start();one.join(); two.join();if (x == 0 && y == 0) {String result = "第" + i + "次,x = " + x + ", y = " + y;System.out.println(result);break;}}}}
仔细观察这段代码,如果没有指令重排序,x,y可能的结果是:1,1(执行顺序:1->3->2->4)、1,0(执行顺序:3->4->1->2)、0,1(执行顺序:1->2->3->4),但是不会出现:0,0 。只有当1和2发生了重排,3和4发生了重排,x,y的结果才会是0,0 。也就是当结果输出0,0时,就一定发生了重排序 。
执行结果(第一次)
看完这篇volatile volatile什么用

文章插图

执行结果(第二次)
看完这篇volatile volatile什么用

文章插图
由于发生重排序的时机不确定,所以程序执行可能很快,也有可能很慢,差异可能非常大 。