面试题答案
一键面试常规懒汉式单例模式问题
常规懒汉式单例模式在多线程高并发环境下,由于多个线程可能同时判断 instance == null
为 true
,从而导致创建多个实例,破坏单例模式。
优化方案
- 同步方法:
在获取实例的方法上添加
synchronized
关键字。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
此方法虽然解决了线程安全问题,但每次调用 getInstance
方法都需要获取锁,在高并发环境下性能较低,因为锁的竞争会成为瓶颈。
- 双重检查锁定(DCL):
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;
}
}
这里使用 volatile
关键字修饰 instance
,保证了其可见性和禁止指令重排序。第一次 if (instance == null)
检查是为了避免不必要的锁竞争,只有在 instance
为 null
时才进入同步块。同步块内再次检查 instance == null
,防止多个线程同时通过第一次检查进入同步块创建多个实例。相比同步方法,DCL 大大提高了性能,只有在第一次创建实例时会有锁竞争。
- 静态内部类:
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式利用了类加载机制,JVM 保证在类加载时的线程安全性。在调用 getInstance
方法时,才会加载 SingletonHolder
类,从而创建单例实例。此方法既保证了线程安全,又避免了同步带来的性能开销,是一种比较优雅的解决方案。
对系统性能和资源的影响
- 同步方法:性能较低,因为每次获取实例都需要竞争锁,导致线程等待,增加了线程上下文切换开销,在高并发场景下可能成为性能瓶颈。但实现简单,对资源消耗主要在锁竞争上。
- 双重检查锁定(DCL):性能较好,只有首次创建实例时会有短暂的锁竞争,后续获取实例无需竞争锁。
volatile
关键字虽有一定开销,但相比同步方法可忽略不计。对资源影响较小,主要开销在首次创建实例时的锁竞争。 - 静态内部类:性能最优,利用类加载机制保证线程安全,无额外同步开销。对资源消耗极少,只在类加载时创建一次实例。