单例模式是一种创建型设计模式,其目的是确保一个类仅有一个实例,并提供一个全局访问点。这种模式常用于那些需要频繁创建和销毁的资源消耗较大的对象,或者需要严格控制实例数量的对象,例如数据库连接池、缓存、日志管理器等。
单例模式的核心要点包括:
- 确保单例:通过私有构造函数、静态内部类、双重检查锁定(DCL)、枚举等机制,防止外部创建额外的实例。
- 全局访问:提供一个公共的静态方法(通常称为
getInstance()
),允许客户端在任何地方通过此方法获取单例对象。
单例模式有多种实现方式,以下是常见的几种:
1、饿汉式(静态常量):
在类加载时立即创建单例对象,线程安全,但可能造成资源浪费(如果单例在整个程序生命周期内并不需要,却在类加载时就被创建)。
Java代码示例:
public class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {}
public static SingletonEager getInstance() {
return INSTANCE;
}
}
2、懒汉式(线程不安全):
在首次调用getInstance()
时创建单例对象,非线程安全。
Java代码示例:
public class SingletonLazyUnsafe {
private static SingletonLazyUnsafe instance;
private SingletonLazyUnsafe() {}
public static SingletonLazyUnsafe getInstance() {
if (instance == null) {
instance = new SingletonLazyUnsafe();
}
return instance;
}
}
3、懒汉式(线程安全,同步方法或同步块):
在首次调用getInstance()
时创建单例对象,并通过同步方法或同步块确保线程安全,但每次获取实例都需要同步,影响性能。
Java代码示例:
public class SingletonLazySynchronizedMethod {
private static SingletonLazySynchronizedMethod instance;
private SingletonLazySynchronizedMethod() {}
public static synchronized SingletonLazySynchronizedMethod getInstance() {
if (instance == null) {
instance = new SingletonLazySynchronizedMethod();
}
return instance;
}
}
4、双重检查锁定(DCL,推荐使用):
在getInstance()
方法中进行两次检查(一次检查实例是否已创建,一次检查是否需要同步),既保证线程安全又减少了同步带来的性能损耗。
Java代码示例:
public class SingletonDCL {
private volatile static SingletonDCL instance;
private SingletonDCL() {}
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
5、静态内部类:
利用类加载机制保证线程安全,实现简单且性能良好。
Java代码示例:
public class SingletonInnerClass {
private SingletonInnerClass() {}
private static class SingletonHolder {
private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();
}
public static SingletonInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
6、枚举:
JDK 1.5 引入的实现方式,简洁且天生线程安全。
Java代码示例:
public enum SingletonEnum {
INSTANCE;
// 可以在此处添加其他方法和属性
}
使用过程中需要注意的问题
线程安全性:确保单例在多线程环境下的正确性,避免多个实例被创建。饿汉式、静态内部类和枚举实现天生线程安全,懒汉式和DCL需要特别关注线程安全问题。
序列化与反序列化:若单例类实现了
Serializable
接口,可能通过反序列化创建新的实例。为防止这种情况,需在单例类中添加readResolve()
方法,返回唯一的单例实例。反射攻击:恶意代码可以通过反射机制调用私有构造函数创建新的实例。为防止这种情况,可以在构造函数中添加防御性编程,如抛出异常或设置标志位验证单例状态。
测试:单例模式可能导致单元测试困难,因为它与其他组件紧密耦合。可以考虑使用依赖注入框架或提供一个测试友好的构造函数(仅在测试环境中启用)来解决这个问题。
资源释放:如果单例持有昂贵资源(如数据库连接、文件句柄等),需要确保在应用程序退出时正确释放资源,避免资源泄漏。可以使用
java.lang.Runtime.addShutdownHook()
注册关闭钩子,或使用try-finally
语句确保资源清理。性能:在高并发场景下,选择性能更好的实现方式,如DCL或静态内部类,避免不必要的同步开销。如果单例创建成本高,可以考虑使用延迟初始化(懒汉式)。
单例模式在确保一个类仅有一个实例的同时提供了全局访问点,适用于需要控制实例数量、共享资源或协调系统行为的场景。在实现和使用单例模式时,要注意线程安全性、序列化与反序列化、反射攻击、测试、资源释放和性能等方面的问题。根据实际需求选择合适的实现方式。