java关键之-volatile解析
volatile关键字,熟悉的朋友应该能总结到该关键字的特征如下:
1.保证可见性、但不保证原子性
2.禁止指令重排
在这里有一些理论的概念需要理解清楚,什么是可见性、原子性?
这一部分的内容其实涉及到Java内存模型(JMM),什么是JMM?本质上可以理解为JVM规范了如何提供按需禁用缓存和编译优化的方法。
在这之前我们知道并发编程bug的源头有:CPU缓存带来的可见性问题、线程切换带来的原子性问题和编译优化带来的有序性问题。
背景:随着科技的不断发展,我们的cpu、内存、I/O设备都在不断迭代,变得越来越快,但是有一个核心的矛盾存在,就是这三者的速度差异。从速度上来说,cpu > 内存 > I/O,但是根据木桶原理(一只水桶能装多少水是取决于最短的那块木板),程序整体的性能是取决于最慢的操作-比如读写I/O设备。为了合理利用CPU的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都作出了贡献,主要表现为:
- CPU增加了缓存,以均衡与内存的速度差异
- 操作系统增加了进程、线程,以分时复用CPU,进而均衡了CPU与I/O设备的速度差异
- 编译程序优化指令执行次序,使得缓存能够得到更加合理的利用
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到,称之为可见性。

原子性:可以理解为一个操作要么全部完成,否则全部失败。
举个案例:代码中常见的cont + 1操作,其实实现起来分为三部,
指令 1:首先,需要把变量 count 从内存加载到 CPU 的寄存器;
指令 2:之后,在寄存器中执行 +1 操作;
指令 3:最后,将结果写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)。
在多线程的情况下,线程A可能执行了步骤1、cpu时间片切换到了线程B开始执行,导致count=1写入到内存中,线程A再此执行count+1=1,将线程内缓存写入到主内存中,结果被覆盖了

有序性:编译器了优化性能,有时候会改变程序中语句的先后顺序。
例如程序中”a = 6;b =7”,可能会变成”b = 7;a =6” 这样可能会导致意想不到的BUG。
一个经典的java案例就是利用双重检查创建单例对象:
public class Singleton { static Singleton instance; static Singleton getInstance(){ if (instance == null) { synchronized(Singleton.class) { if (instance == null) instance = new Singleton(); } } return instance; } }
在多线程调用时会存在问题,问题出在new操作上。
- a. 给singleton分配内存
- b. 在内存中初始化singleton对象
- c. 将内存地址赋给singleton变量

目前多采用静态内部类无锁的单例模式
public class Singleton { private Singleton(){}; static class innerHolder{ private static Singleton instance = new Singleton(); } public static Singleton getInstance(){ return innerHolder.instance; } }
特点:静态内部类实现单例模式,既能保证延迟加载,又能保证线程安全,只创建一个实例对象。
原理解析:Singleton类初始化的时候不会加载所有的类,只有第一次访问静态内部类的时候才会进行初始化操作。

浙公网安备 33010602011771号