面试官:说说volatile应用和实现原理?
面试官:说说volatile应用和实现原理?
volatile是Java并发编程中的重要关键字,它与synchronized、ReentrantLock等齐名,属于并发编程五杰之一。本文将详细介绍volatile的基本概念、工作原理、适用场景以及局限性,帮助读者更好地理解volatile在并发编程中的作用和限制。
什么是 volatile?
volatile是Java中的一个关键字,用于修饰变量,它的主要作用是保证变量的可见性和禁止指令重排序。
- 可见性:是指当一个线程修改了一个被volatile修饰的变量时,其他线程能够立即看到这个修改。
- 禁止指令重排序:则是确保对volatile变量的读写操作不会被编译器或处理器随意重新排序,从而保证了程序执行的顺序符合我们的预期。
volatile 工作原理
为了实现可见性,Java内存模型(JMM)会在对volatile变量进行写操作时,强制将工作内存中的值刷新到主内存,并在读取时强制从主内存中重新获取最新的值。而禁止指令重排序是通过在编译器和处理器层面添加特定的内存屏障指令来实现的。
2.1 可见性实现原理
在计算机编程特别是多线程编程中,“可见性”指的是一个线程对共享变量的修改,对于其他线程是否能够及时地、准确地“可见”,即其他线程是否能够及时感知到这个修改并获取到最新的值。
多线程操作共享变量流程如下:
volatile是通过内存屏障(Memory Barrier)来确保可见性。
- 写屏障(Store Barrier):在volatile变量的写操作之后插入写屏障,确保所有之前的写操作都同步到主内存中,从而使得其他线程在读取该变量时能够获取到最新的值。
- 读屏障(Load Barrier):在volatile变量的读操作之前插入读屏障,确保所有之前的写操作都已完成,从而读取到的是最新的值。
通过这种方式,volatile变量在多线程环境下的读写操作能够保持较高的可见性,但需要注意的是,volatile并不保证操作的原子性。
2.2 有序性实现原理
volatile的有序性是通过插入内存屏障,在内存屏障前后禁止重排序优化,以此实现有序性的。
2.3 正确理解“内存屏障”?
volatile保证可见性的“内存屏障”和保证有序性的“内存屏障”有什么区别呢?
在说它们的区别之前,我们现需要对“内存屏障”有一个大致的理解。
内存屏障,简单来说,就像是在内存操作中的一道“关卡”或者“栅栏”。想象一下,计算机在执行程序的时候,为了提高效率,可能会对指令的执行顺序进行一些调整。但是在多线程或者多核心的环境下,这种随意的调整可能会导致一些问题。
内存屏障的作用就是阻止这种随意的调整,确保特定的内存操作按照我们期望的顺序执行。
所以“内存屏障”本身只是一种“技术”,而这种“技术”可以实现很多“业务功能”。这就像Spring中的AOP一样,AOP是一种“技术”,而这种技术可以实现很多业务功能。例如,针对日志处理可以使用AOP、针对用户鉴权可以使用AOP等,而内存屏障也是一样,我们可以使用内存屏障实现可见性的“业务功能”,也可以实现有序性的“业务功能”等。
volatile 适用场景
volatile常见场景有以下两种:
- 状态标记
- 单例模式中的双重检查锁
3.1 状态标记
例如,在多线程环境中用于表示某个任务是否完成的标志变量,具体代码如下:
volatile boolean isTaskFinished = false;
3.2 单例模式中的双重检查锁
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile 局限性
volatile并不能保证原子性,也就是并不能保证线程安全。例如,对于i++这样的操作,它不是一个原子操作,单纯使用volatile修饰i并不能保证线程安全。
课后思考
为什么双重效验锁一定要加volatile?不是已经加锁了吗?