面试题答案
一键面试反模式场景
- 双重检查锁定(DCL)问题:在早期的Java中,使用双重检查锁定实现单例,由于指令重排序,可能会导致在对象未完全初始化时就被其他线程访问。例如:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 这里可能发生指令重排
}
}
}
return instance;
}
}
在 instance = new Singleton()
这行代码中,对象的创建可能分为三步:分配内存空间、初始化对象、将 instance
指向分配的内存地址。由于指令重排,可能会先执行 1 和 3,然后执行 2。此时,如果另一个线程进入 getInstance
方法,在第一次检查 instance == null
时,发现 instance
不为空,但实际上对象还未初始化完成,从而导致错误。
- 静态内部类延迟加载的错误使用:虽然静态内部类实现单例能保证线程安全,但如果在静态内部类中包含一些非线程安全的操作,比如修改共享的静态变量,就会出现问题。例如:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
// 假设这里有一个共享的静态变量且非线程安全操作
private static int sharedValue = 0;
public static void incrementSharedValue() {
sharedValue++;
}
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
如果多个线程同时调用 SingletonHolder.incrementSharedValue()
,由于 sharedValue
没有同步机制,会导致数据不一致。
修正方法
- 使用
volatile
关键字解决 DCL 问题:对instance
加上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;
}
}
- 解决静态内部类中共享变量的线程安全问题:对共享变量的操作进行同步。例如:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
private static int sharedValue = 0;
public static synchronized void incrementSharedValue() {
sharedValue++;
}
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
另外,还可以使用以下方式确保单例的线程安全:
- 饿汉式单例:在类加载时就创建实例,天然线程安全。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
- 使用
Enum
实现单例:Java 枚举类型本身就保证了线程安全和单例性。
public enum Singleton {
INSTANCE;
// 可以在这里添加方法
public void someMethod() {
System.out.println("执行方法");
}
}