1.关键字 volatile是Java虚拟机提供的最轻量级的同步机制
2.当一个变量被定义称volatile之后,它将具备两项特性:
1)保证此变量对所有线程可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程可以立即得知
volatile变量在各个线程的工作内存中是不存在一致性问题的(从物理存储角度看,各个线程的工作内存中volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),
但是JAVA里面的运算符操作并非原子操作,这导致volatile变量的运算在并发下一样的不安全
由于volatile变量只能保证可见性,在不符合一下两条规则的运算场景中,我们仍然需要通过枷锁(使用synchoronized、java.util.concurrent中的锁或原子类)来保证原子性
- 运算结果并不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值:这里不依赖指各自独立运算,不以当前变量为基础进行运算;要么单线程运算
- 变量不需要与其他的状态变量共同参与不变约束
2)禁止指令重排
例如:B线程依赖于A线程的配置
Map configOptions; char[] configText; // 此变量必须定义为volatile volatile boolean initialized = false; // 假设以下代码在线程A中执行 // 模拟读取配置信息,当读取完成后,将initialized设置为true,通知其他线程配置可用 configOptions = new HashMap(); configText = readConfigFile(fileName); processConfigOptions(configtext, configOptions); initialized = true; -------------------------------------------------------------------------- // 假设以下代码在线程B中执行 // 等待initialized 为true,代表线程A已经把配置信息初始化完成 while(!initialized) { sleep(); } // 使用线程A中初始化好的配置信息 doSomethingWithConfig();
如果定义 initialized变量时没有使用volatile修饰,就可能会由于指令重排序的优化,导致位于线程A中最后一条代码 “initialized = true;”被提前执行,可能会放在代码块的第一行执行,这样在B线程使用配置信息的代码就可能出现错误
3.针对long和double型变量的特殊规则
Java内存模型要求lock unlock read load use assingn store write这八中操作都有原子性,但是对于64为的数据类型(long和double),在模型中特别定义了一条宽松的规定:
允许虚拟机没有被volatile修饰的64位数据的读写操作划分位两次32位的操作来进行,即允许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这个四个操作的原子性
如果多线程共享一个并未声明位volatile的long或double类型的变量,并且同时对他们进行读取和修改操作,那么某些线程可能会读取到一个既不是原值,也不是其他线程修改值的代表了“半个变量”的数值。在64位不会出现,但在32位x86平台下的虚拟机存在这种风险(非原子访问的风险)