在介绍volatile之前,先简单了解一下Java内存模型。在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果,笔者认为是定义了程序中变量的访问规则。
所有线程共享的区域,存储线程共享的数据,包括实例变量、静态变量和构成数组的对象的元素,不包括局部变量和方法参数。
每个线程独享的区域,存储主内存中的数据拷贝。
Java内存模型规定所有的变量都是存在主内存中,每个线程都有自己的工作内存。每个线程对共享数据的读、写操作都只在各自的工作内存中,不能直接在主内存中进行;在各自工作内存中对数据操作完成后,同步到主内存中;线程间不能访问各自工作内存中的数据,只能通过主内存来完成。下面用一张图来展示线程、主内存和工作内存三者之间的交互关系。
当使用volatile关键字修饰共享变量(实例变量、静态变量)时,它将具备两个特性:可见性和禁止指令重排序优化。
变量被修改后的新值会立即写回主内存中,同时会使其它线程工作内存中的旧值失效,新值对其它线程来说是可见的,其它线程可以立即得到修改后的新值,因为volatile变量每次使用之前都需要从主内存刷新获取最新值。
关于volatile实现的可见性可能会误解,认为既然volatile变量所有的写操作都会立刻反应到其它线程中,那么对volatile变量进行并发操作就是安全的。有这个误解是因为忽略了原子性,volatile是不保证原子性的。对一个变量进行修改赋值操作,可能写的就是一条简单的i=i+1,但是底层实现上会需要多条字节码指令来完成,同时一条字节码指令也可能转化成多条机器码指令,在并发情况下,这些指令的执行不能保证原子性。
指令重排序是指CPU在正确处理指令依赖(数据依赖)并且保障程序执行得到正确结果的情况下,调整代码的执行顺序,允许将多条指令不按照程序规定顺序分开发送给各相应电路单元处理。需要注意的是指令重排序不会影响到代码在单线程环境下的执行,会影响到多线程并发情况下执行的正确性。
volatile禁止指令重排序是通过lock前缀指令实现的,lock前缀的指令相当于一个内存屏障,指令重排序时不能把后面的指令重排序到lock前缀指令之前,同时它会强制将对工作内存的修改操作立即写入主内存中。
虽然volatile可以实现最轻量级的同步机制,但是使用volatile修饰的变量必须满足以下两个条件: