Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,
并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。
当volatile用于一个作用域时,Java保证如下:
1.(适用于Java所有版本)读和写一个volatile变量有全局的排序。也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,
而不是(可能)使用一个缓存的值。(但是并不保证经常读写volatile作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。
2.(适用于Java5及其之后的版本)volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁。
使用volatile会比使用锁更快,但是在一些情况下它不能工作。volatile使用范围在Java5中得到了扩展,特别是双重检查锁定现在能够正确工作。
volatile可以用在任何变量前面,但不能用于final变量前面,因为final型的变量是禁止修改的。也不存在线程安全的问题。
了解volatile关键字关键字之前,首先了解下原子操作和happens-before关系的相关概念。
原子操作
原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。
原子操作在操作完毕之前不会线程调度器中断。在Java中,对除了long和double之外的基本类型的简单操作都具有原子性。
简单操作就是赋值或者return。比如”a = 1;“和 “return a;”这样的操作都具有原子性。但是在Java中,类似“a += b”这样的操作不具有原子性,
所以如果方法不是同步的就会出现难以预料的结果。在某些JVM中”a += b”可能要经过这样三个步骤:
1.取出a和b。2.计算a+b。3.将计算结果写入内存。
如果有两个线程t1,t2在进行这样的操作。t1在第二步做完之后还没来得及把数据写回内存就被线程调度器中断了,于是t2开始执行,
t2执行完毕后t1又把没有完成的第三步做完。这个时候就出现了错误,相当于t2的计算结果被无视掉了。
类似的,像“a++”这样的操作也都不具有原子性。所以在多线程的环境下一定要记得进行同步操作。
要搞清楚这个问题,首先应该明白计算机内部都做什么了。比如做了一个i++操作,计算机内部做了三次处理:读取-修改-写入。
同样,对于一个long型数据,做了个赋值操作,在32系统下需要经过两步才能完成,先修改低32位,然后修改高32位。
假想一下,当将以上的操作放到一个多线程环境下操作时候,有可能出现的问题,是这些步骤执行了一部分,而另外一个线程就已经引用了变量值,
这样就导致了读取脏数据的问题。