这道题想考察什么?

  1. 是否了解volatile的使用?
  2. 是否了解volatile的特性与实现原理?

考察的知识点

  1. volatile的使用
  2. JMM
  3. 指令重排
  4. volatile的实现原理

考生应该如何回答

1、首先,我们简单介绍一下volatile关键字。不过,在解释volatile前,我们需要了解并发编程的3个基本概念。

  • 原子性:一个操作或者多个操作,要么就全部执行完,要么就都不执行。
  • 可见性:多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  • 有序性:程序执行的顺序按照代码的先后顺序执行。

volatile是Java提供的一个轻量级同步机制,作为并发编程里一个重要组成部分,它用来修饰变量。通过volatile修 饰的变量可以保证可见性与有序性。在双重检查加锁方式实现的单例中,就有使用,看下面这个再也不能更熟悉的单例代码。

public class ClientService extends BaseService {    //使用volatile修饰单例变量    private volatile static ClientService sInstance;    //获取单例,双重检查加锁方式    public static ClientService getInstance() {        if (sInstance == null) {            synchronized (ClientService.class) {                if (sInstance == null) {                    //注意!!!这里看似一行代码,其实分三步走:                    //第一步:为ClientService对象分配内存                    //第二步:实例化对象                    //第三步:将sInstance引用指向实例                    sInstance = new ClientService();                }            }        }        return sInstance;    }    //构造方法私有    private ClientService() {    }}

2、接下来,我们就可以针对volatile的特性来一一解释,关于可见性问题需要从Java内存模型这块入手,简称JMM。

java volatile使用场景 java中volatile的作用_有序性

在JMM中,为了提高效率,抽象出一个主内存与工作内存的概念。线程之间的共享变量存储在主内存中,另外每个线程又都配备了一个私有的工作内存,工作内存中使用到的变量需要到主内存去拷贝,线程对变量的读取、赋值操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成。

那volatile如何保证可见性的呢?

当我们的变量被volatile修饰后,在生成汇编代码指令时会在volatile修饰的共享变量进行写操作的时候添加一个Lock前缀。

Lock前缀表示当一个线程修改该共享变量后,它会将新值立即刷新到主内存中,同时导致该变量在所有线程的本地内存失效,这样其他线程再读取共享变量时,会直接从主内存中读取,达成缓存一致性。这里与synchronize或者Lock等锁机制保证可见性的做法还是有差别的。锁机制的做法是保证同一时刻只有一个线程获取锁并执行同步代码,释放锁时将对变量的修改刷新到主存当中。

3、说完可见性,再来看看有序性。有序性问题需要归咎于指令重排。在Java内存模型中,如果某些指令之间不存在数据依赖,为了提高效率,是允许编译器和CPU对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。我们可以简单的这样理解,打个比方:

//第一种情况,a与b没有依赖关系int a = 2;int b = 3;//第二种情况,a与b没有依赖关系,但是c依赖a和bint a = 2;int b = 3; int c = a + b;

对于第一种情况,a与b之间没有数据依赖关系,机器指令就会进行重新排序,可能先会执行b的赋值,再去执行a的赋值,这样单线程肯定没问题,但是如果有另一个线程同时在使用变量b,就有可能引发并发问题。再看第二种情况,因为c依赖a和b的值,所以在这种情况下,机器指令就不会把c的赋值排到在a和b的前面。

那volatile如何保证有序性的呢?

为了避免指令重排引起的并发问题,volatile就大派用场了。volatile的实现具体是这样做的,在Java编译器生成指令时,对于volatile关键字修饰的变量,会在指令序列中插入特定的内存屏障。

内存屏障又是什么呢?其实它并不是什么高深的东西,说到底也是一个指令而已,例如StoreStore、StoreLoad、LoadLoad、LoadStore等,它特殊的地方,通俗来讲就是告诉编译器和CPU,不管什么指令都不能和我的内存屏障指令重排序,大致就是这个意思,关于内存屏障相关内容,这里就不过多赘述了。

总而言之,记住一点,使用volatile关键字修饰的变量会禁止指令重排,从而保证了有序性。

总结

最后,我们还是总结一下,这道题的答案其实很简单,一两句话便可以概括,volatile就是保证了可见性与有序性。当然,volatile的内部原理还是需要深究一下的,指不定面试官要跟你往深处唠唠呢!