【面试题Java】单例模式
单例模式的实现方法
- 饿汉式:在类加载时就立即创建单例实例,线程安全,实现简单。代码示例如下:
public class Singleton {
// 私有静态实例,在类加载时就创建
private static final Singleton instance = new Singleton();
// 私有构造函数,防止外部实例化
private Singleton() {}
// 公共的静态方法,用于获取单例实例
public static Singleton getInstance() {
return instance;
}
}
- 懒汉式(线程不安全):在第一次调用
getInstance
方法时才创建实例,实现较为简单,但在多线程环境下可能会创建多个实例。代码示例如下:
public class Singleton {
// 私有静态实例,初始化为null
private static Singleton instance = null;
// 私有构造函数,防止外部实例化
private Singleton() {}
// 公共的静态方法,用于获取单例实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 懒汉式(线程安全):通过
synchronized
关键字保证在多线程环境下只有一个线程能创建实例,但性能可能会受影响。代码示例如下:
public class Singleton {
// 私有静态实例,初始化为null
private static Singleton instance = null;
// 私有构造函数,防止外部实例化
private Singleton() {}
// 公共的静态方法,用于获取单例实例,添加synchronized关键字保证线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 双重检查锁(DCL):在
getInstance
方法中进行两次null
检查,提高了性能,同时保证线程安全。代码示例如下:
public class Singleton {
// 私有静态实例,使用volatile关键字保证可见性和禁止指令重排序
private static volatile Singleton instance = null;
// 私有构造函数,防止外部实例化
private Singleton() {}
// 公共的静态方法,用于获取单例实例
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 静态内部类:利用静态内部类的特性,在外部类加载时,静态内部类不会被加载,只有在调用
getInstance
方法时才会加载内部类并创建实例,线程安全且性能较好。代码示例如下:
public class Singleton {
// 私有构造函数,防止外部实例化
private Singleton() {}
// 静态内部类,用于持有单例实例
private static class SingletonHolder {
// 私有静态实例,在类加载时创建
private static final Singleton instance = new Singleton();
}
// 公共的静态方法,用于获取单例实例
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
- 枚举:通过枚举类型实现单例,简洁且天然支持序列化和线程安全。代码示例如下:
public enum Singleton {
// 唯一的实例
INSTANCE;
// 可以添加其他方法和属性
public void doSomething() {
// 具体实现
}
}
破坏单例的方式
- 反射:通过反射机制可以调用私有构造函数来创建新的实例,从而破坏单例。代码示例如下:
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
// 获取单例类的Class对象
Class<Singleton> clazz = Singleton.class;
// 获取私有构造函数
Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
// 允许访问私有构造函数
constructor.setAccessible(true);
// 通过构造函数创建新的实例
Singleton instance1 = constructor.newInstance();
// 获取单例实例
Singleton instance2 = Singleton.getInstance();
// 比较两个实例是否相同
System.out.println(instance1 == instance2);
}
}
- 反序列化:如果单例类实现了
Serializable
接口,在反序列化时会创建一个新的实例,从而破坏单例。代码示例如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Main {
public static void main(String[] args) throws Exception {
// 创建单例实例
Singleton instance1 = Singleton.getInstance();
// 将单例实例序列化到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"))) {
oos.writeObject(instance1);
}
// 从文件中反序列化出单例实例
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"))) {
Singleton instance2 = (Singleton) ois.readObject();
// 比较两个实例是否相同
System.out.println(instance1 == instance2);
}
}
}
class Singleton implements Serializable {
// 私有静态实例
private static final Singleton instance = new Singleton();
// 私有构造函数
private Singleton() {}
// 公共的静态方法,用于获取单例实例
public static Singleton getInstance() {
return instance;
}
// 反序列化时,返回单例实例
private Object readResolve() {
return instance;
}
}
- 对象克隆:如果单例类实现了
Cloneable
接口,并且没有正确处理克隆方法,通过克隆也可以创建新的实例,从而破坏单例。代码示例如下:
public class Main {
public static void main(String[] args) throws Exception {
// 获取单例实例
Singleton instance1 = Singleton.getInstance();
// 克隆单例实例
Singleton instance2 = (Singleton) instance1.clone();
// 比较两个实例是否相同
System.out.println(instance1 == instance2);
}
}
class Singleton implements Cloneable {
// 私有静态实例
private static final Singleton instance = new Singleton();
// 私有构造函数
private Singleton() {}
// 公共的静态方法,用于获取单例实例
public static Singleton getInstance() {
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// 如果不重写clone方法,默认会创建一个新的实例
return super.clone();
}
}
为了防止反射和反序列化破坏单例,可以在单例类的私有构造函数中添加逻辑,判断是否已经创建过实例,如果是则抛出异常。对于对象克隆,可以重写clone
方法,返回单例实例而不是创建新的实例。对于枚举类型的单例,由于其本身的特性,天然防止了反射、反序列化和对象克隆的破坏。