原标题:举例子详细彻底说明java的volatile关键字工作原理

volatile关键字的含义

在Java中用volatile关键字修饰某变量,Java编译器和线程不会缓存该变量,并总是从主存中读取该变量。

volatile关键字可以保证可见性和执行顺序,可见性既是先发生的原子修改操作一定会在读操作之前执行完成(同一时间对volatile变量只能有一个操作);执行顺序的含义既是volatile关键字会阻止编译器或JVM对代码重排序,或把volatile变量从同步区域(synchronized方法,synchronized代码块..)中移除出来。

Java的内存模型

Java的内存模型(JMM)可以保证一个线程修改了某个变量,该变量的改变对另一个线程是可见的,也就是说另一个线程取得的这个变量的值,一定是前一个线程修改之后的值。这个也被称作"happens-before" 。

第一个例子说明volatile变量的含义

java volatile 性能 java volatile 例子_java volatile 性能

第一个例子说明volatile变量的含义

这是单例的懒汉模式。

这里的问题是,

if (_instance == null) {

不是同步的。假设线程A创建了_instance,即使它退出了synchronized代码块,内存也并没有进行同步,修改了的_instance变量没有更新到主存中,所以其他线程执行到

if (_instance == null) {

语句,以为_instance还是null的,就会再次进入synchronized代码块。

在这个例子中,去掉volatile修饰符程序也不会出现什么问题,只是会多次进去synchronized代码块而已——这就是双重检查单例模式的bug,如果线程老是要进入同步块,那么同一时间就只能有一个线程在运行,效率是很低的,使用双重检查就是为了解决这个问题,如果去掉volatile关键字,结果就不如人意了——不使用volatile关键字,线程就有可能缓存了_instance的值,结果所有缓存了_instance值的线程第一次获取_instance都需要进入同步块,效率很低。

那么我们可以去掉synchronized关键字吗?

java volatile 性能 java volatile 例子_java volatile 性能_02

同学们答案是什么?

java volatile 性能 java volatile 例子_代码块_03

不可以去掉synchronized关键字,如果某个线程在执行完之后失去了时间片,另一个线程开始执行判断语句,则至少有两个线程会new Singleton()。

第二个例子说明volatile关键字的含义

java volatile 性能 java volatile 例子_主存_04

上面的代码如果并发执行,假设有一个线程从主存中获取了"bExit"的值,并缓存起来,之后有其他线程修改了bExit并更新到主存中,这个时候,这个线程中的bExit还是之前获取的值,而不是其他线程修改之后的值。这是有问题的,需要用volatile修饰bExit。

volatile关键字使用场所

volatile关键字只能修饰变量,不能修饰类,也不能修饰方法。

想要把某个变量共享,该变量的读写操作必须是原子性的,并用volatile关键字修饰。

volatile修饰的long和double类型的变量读写操作是原子性的。long和double都是64位的,long和double类型变量赋值操作与平台相关,在有些平台上不是原子操作。很多平台给long和double变量赋值需要2步操作,每一步只写32位,在这2个步骤之间,如果其他线程获取到long或double类型的变量的值,则这时候读取的变量的状态是不正确的。

volatile变量有类似synchronized同步代码的可见性,即每个线程读取到的都是最新更新的值。volatile的局限性很大,你只能获取volatile变量的值,直接设置volatile变量的值,不能比较之后再设置volatile的值,因为在你做比较操作的区间很有可能有其他线程修改了该volatile变量的值。

被volatile关键字修饰,表明该变量是要被多个线程访问的,编译器不会对与volatile变量相关的代码做重排或其他多线程下不允许的优化。没有被volatile关键字修饰,编译器会重排代码,缓存变量的值,减少从主存中直接获取变量的值。

java volatile 性能 java volatile 例子_缓存_05

不用volatile关键字修饰,有可能线程可以获取到另一个线程设置的isActive的值,编译器有可能缓存了isActive的值等等,使用volatile关键字可以避免这些情况。

双重检查单例模式在JDK4是有问题的,volatile可以修复这个问题。本文的《第一个例子说明volatile变量的含义》中举的例子就是说这个问题。

volatile关键字的总结

1. volatile关键字只用修饰变量,不能修饰方法和类。

2. volatile变量的值都是从主存中获取的,而不是从线程的本地内存。

3.long和double变量被volatile关键字修饰之后,读写(赋值操作,读取操作)都是原子操作.

4. 使用volatile关键字可以避免内存不一致的错误;写入volatile变量一定会比接下来的读操作先发生。

5. 从jdk5开始对volatile变量的修改对其他的线程都是可见的;当线程读取volatile变量的时候,会先把其他线程中缓存着的volatile变量(如果还没有更新到主存中的时候)强制写入到主存。

6. 除了long和double其他的基本类型读写操作都是原子性的;引用类型的读写操作也是原子性的。

7. volatile变量只能做简单的读写,没有锁,没有阻塞。

8.volatile变量可以是空的.

9. volatile不能保证原子性,比如volatile修饰的int变量++操作还是非原子的。

10. 变量没有在多个线程之间共享,没有必要做任何同步的操作,比如使用volatile关键字修饰。

synchronized和volatile关键字的比较

volatile关键字代替不了synchronized关键字,不过在某些场合可以作为替代方案。

1. volatile关键字只能修饰字段,而synchronized只能修饰代码块和方法。

2. synchronized关键字需要获得锁释放锁,volatile关键字不需要。

3.synchronized代码块或方法在等待锁的时候会被阻塞;volatile不是这样的。

4. synchronized代码块或方法会比volatile关键字更影响性能。

5. volatile关键只同步被修饰的变量,而synchronized关键字却同步代码块或方法中所有的变量,并且还会获得锁释放锁,所以synchronized的负载更大。

6. 不能synchronizednull对象,而volatile变量可以是null的。

  1. 读取volatile变量效果等同获取锁,写入volatile变量效果等同释放锁。