Java 设计模式:单例模式的 10 种实现方式及优缺点
Java 设计模式:单例模式的 10 种实现方式及优缺点
单例模式(Singleton Pattern)是设计模式中最为经典的模式之一,其核心思想是确保一个类只有一个实例,并提供一个全局访问点。单例模式在实际开发中非常常见,比如配置管理器、日志记录器、线程池等场景。本文将详细探讨 Java 中单例模式的 10 种实现方式,并分析每种方式的优缺点。
单例模式简介
单例模式的实现需要满足以下三个条件:
- 私有化构造方法:防止外部通过
new
关键字创建实例。 - 提供一个静态的私有实例:确保类只有一个实例。
- 提供一个公共的静态方法:用于获取唯一的实例。
单例模式可以分为“懒汉式”和“饿汉式”两大类,其中懒汉式在需要时才创建实例,而饿汉式在类加载时就创建实例。此外,还有一些更高级的实现方式,比如使用枚举、静态内部类等。
懒汉式单例(非线程安全)
懒汉式单例是最简单的实现方式,它在第一次调用 getInstance()
方法时才创建实例。这种方式的优点是延迟加载,但缺点是线程不安全。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
优点
- 延迟加载,节省资源。
缺点
- 在多线程环境下,可能会创建多个实例。
懒汉式单例(线程安全)
为了保证线程安全,可以在 getInstance()
方法上加 synchronized
关键字。
public class ThreadSafeLazySingleton {
private static ThreadSafeLazySingleton instance;
private ThreadSafeLazySingleton() {}
public static synchronized ThreadSafeLazySingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeLazySingleton();
}
return instance;
}
}
优点
- 线程安全。
缺点
- 每次调用
getInstance()
都需要加锁,性能较差。
双重检查锁定(Double-Checked Locking)
双重检查锁定是一种优化的懒汉式实现方式,它通过两次检查实例是否为 null
来减少锁的使用,从而提高性能。
public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
优点
- 线程安全,且只在第一次创建实例时加锁,性能较高。
缺点
- 实现稍微复杂,需要理解
volatile
的作用。
饿汉式单例
饿汉式单例在类加载时就创建实例,因此是线程安全的,但无法实现延迟加载。
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
优点
- 线程安全,实现简单。
缺点
- 无法延迟加载,可能导致资源浪费。
静态内部类实现
静态内部类是一种优雅的单例实现方式,它利用 Java 的类加载机制来保证线程安全,同时实现延迟加载。
public class StaticInnerClassSingleton {
private static class Holder {
private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
private StaticInnerClassSingleton() {}
public static StaticInnerClassSingleton getInstance() {
return Holder.instance;
}
}
优点
- 线程安全,延迟加载,实现简单。
缺点
- 需要理解静态内部类的加载机制。
枚举实现单例
枚举是 Java 中实现单例的一种推荐方式,它天然支持线程安全,并且可以防止反射攻击。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("EnumSingleton is working");
}
}
优点
- 线程安全,防止反射攻击,实现简单。
缺点
- 不支持延迟加载,且枚举的使用场景有限。
容器式单例
容器式单例适用于管理多个单例对象的场景,通常使用 Map
来存储实例。
public class ContainerSingleton {
private static Map<String, Object> instances = new ConcurrentHashMap<>();
private ContainerSingleton() {}
public static Object getInstance(String className) {
if (!instances.containsKey(className)) {
synchronized (ContainerSingleton.class) {
if (!instances.containsKey(className)) {
try {
instances.put(className, Class.forName(className).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return instances.get(className);
}
}
优点
- 可以管理多个单例对象。
缺点
- 实现较为复杂。
反射攻击的防御
为了防止反射破坏单例模式,可以在构造方法中抛出异常。
public class ReflectionSafeSingleton {
private static final ReflectionSafeSingleton instance = new ReflectionSafeSingleton();
static {
// 防止反射攻击
ReflectionSafeSingleton.class.getDeclaredConstructors()[0].setAccessible(false);
}
private ReflectionSafeSingleton() {
if (instance != null) {
throw new RuntimeException("Singleton instance already exists");
}
}
public static ReflectionSafeSingleton getInstance() {
return instance;
}
}
优点
- 防止反射攻击。
缺点
- 实现较为复杂。
序列化与反序列化安全
为了防止序列化和反序列化破坏单例,可以重写 readResolve
方法。
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final SerializableSingleton instance = new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() {
return instance;
}
// 防止反序列化创建新实例
protected Object readResolve() {
return instance;
}
}
优点
- 防止序列化和反序列化破坏单例。
缺点
- 需要实现
Serializable
接口。
总结
单例模式的实现方式多种多样,每种方式都有其适用场景和优缺点。在实际开发中,可以根据需求选择合适的实现方式:
- 简单场景:推荐使用静态内部类或枚举实现。
- 需要延迟加载:使用双重检查锁定。
- 需要管理多个单例:使用容器式单例。
- 需要防止反射攻击:使用反射安全的单例实现。
- 需要序列化安全:使用序列化安全的单例实现。
理解单例模式的多种实现方式及其优缺点,可以帮助我们更好地应对实际开发中的各种需求。
- 点赞
- 收藏
- 关注作者
评论(0)