面试题答案
一键面试- 双重检查锁定实现单例模式线程安全原理:
- 双重检查锁定(Double - Checked Locking)是一种在多线程环境下实现高效单例模式的技术。它的核心思想是在获取单例实例时,先进行一次快速的检查,判断实例是否已经创建,如果已经创建则直接返回,避免不必要的同步开销。如果实例未创建,则进入同步块进行创建操作。在同步块内部,再次检查实例是否已经创建,这就是所谓的双重检查。
- 为什么要进行两次检查:
- 第一次检查:主要目的是提高效率。在多线程环境下,很多线程可能同时调用获取单例实例的方法。如果没有第一次检查,每次调用都需要进入同步块,而同步操作是比较消耗性能的。通过第一次检查,大部分情况下(实例已经创建),线程可以直接返回实例,无需进入同步块,从而大大提高了性能。
- 第二次检查:当多个线程通过第一次检查并进入同步块时,由于同步块一次只能允许一个线程进入,所以第一个进入同步块的线程会创建实例,而后续进入同步块的线程如果没有第二次检查,就会再次创建实例,导致单例模式失效。第二次检查确保了即使多个线程同时通过第一次检查进入同步块,也只有一个线程会真正创建实例。
- volatile关键字在此处的作用:
- 在Java中,volatile关键字主要有两个作用:禁止指令重排序和保证内存可见性。
- 禁止指令重排序:在创建对象时,JVM可能会进行指令重排序优化。正常情况下创建对象的步骤是:①分配内存空间;②初始化对象;③将对象引用指向分配的内存空间。但是JVM可能会将②和③的顺序重排为①③②。如果发生这种重排,当一个线程执行到③时,另一个线程通过第一次检查发现实例不为空(因为引用已经指向内存空间),就会直接返回这个未初始化完成的实例,导致程序出错。使用volatile关键字修饰单例实例变量,可以禁止这种指令重排序,保证按照①②③的顺序执行。
- 保证内存可见性:当一个线程修改了volatile修饰的变量后,会立即将修改后的值刷新到主内存中,其他线程在读取该变量时,会从主内存中获取最新的值。在单例模式中,保证了其他线程能够及时看到单例实例的创建,避免获取到旧的或者未初始化完成的实例。
示例代码如下:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}