单例模式
大约 6 分钟
单例模式
什么是单例模式
单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的核心思想是:
- 某个类只能有一个实例
- 类必须自行创建这个实例
- 类必须自行向整个系统提供这个实例
为什么需要单例模式
在实际开发中,有些对象我们只需要一个实例就够了,比如:
- 数据库连接池
- 线程池
- 缓存
- 日志对象
- 配置对象
如果创建多个实例,不仅浪费系统资源,还可能导致程序行为不一致。
单例模式的实现方式
1. 饿汉式(线程安全)
/**
* 饿汉式单例模式
* 在类加载时就创建实例,线程安全但可能造成资源浪费
*/
public class EagerSingleton {
// 在类加载时就创建实例
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造函数,防止外部实例化
private EagerSingleton() {}
// 提供全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
public void doSomething() {
System.out.println("饿汉式单例执行业务逻辑");
}
}
优点:线程安全,实现简单 缺点:无论是否使用都会创建实例,可能造成资源浪费
2. 懒汉式(线程不安全)
/**
* 懒汉式单例模式(线程不安全)
* 在第一次调用时才创建实例,节省资源但线程不安全
*/
public class LazySingleton {
// 声明实例但不创建
private static LazySingleton instance;
// 私有构造函数
private LazySingleton() {}
// 第一次调用时创建实例
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
public void doSomething() {
System.out.println("懒汉式单例执行业务逻辑");
}
}
优点:延迟加载,节省资源 缺点:线程不安全,在多线程环境下可能出现多个实例
3. 懒汉式(线程安全,同步方法)
/**
* 懒汉式单例模式(线程安全,同步方法)
* 通过同步方法保证线程安全
*/
public class SynchronizedLazySingleton {
private static SynchronizedLazySingleton instance;
private SynchronizedLazySingleton() {}
// 同步方法,保证线程安全
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}
public void doSomething() {
System.out.println("同步懒汉式单例执行业务逻辑");
}
}
优点:线程安全,延迟加载 缺点:同步方法效率低,每次调用都需要同步
4. 双重检查锁定(推荐)
/**
* 双重检查锁定单例模式
* 既保证线程安全又提高性能
*/
public class DoubleCheckSingleton {
// 使用volatile关键字确保多线程环境下的可见性
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance() {
// 第一次检查,避免不必要的同步
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
// 第二次检查,确保只创建一个实例
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
public void doSomething() {
System.out.println("双重检查锁定单例执行业务逻辑");
}
}
优点:线程安全,延迟加载,性能较好 缺点:实现相对复杂
5. 静态内部类(推荐)
/**
* 静态内部类单例模式
* 利用类加载机制保证线程安全和延迟加载
*/
public class StaticInnerClassSingleton {
// 私有构造函数
private StaticInnerClassSingleton() {}
// 静态内部类
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
// 提供全局访问点
public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
public void doSomething() {
System.out.println("静态内部类单例执行业务逻辑");
}
}
优点:线程安全,延迟加载,实现简单 缺点:无明显缺点
6. 枚举单例(最推荐)
/**
* 枚举单例模式
* 最安全的单例实现方式
*/
public enum EnumSingleton {
// 定义一个枚举实例
INSTANCE;
public void doSomething() {
System.out.println("枚举单例执行业务逻辑");
}
}
// 使用方式
// EnumSingleton.INSTANCE.doSomething();
优点:线程安全,防止反射攻击,防止序列化破坏 缺点:与其他单例实现方式不一致
单例模式的应用场景
- 配置管理:应用程序的配置信息通常只需要一个实例
- 日志管理:日志对象通常全局共享
- 数据库连接池:连接池管理器只需要一个实例
- 线程池:线程池管理器通常使用单例
- 缓存管理:缓存管理器通常使用单例
- 对话框:系统中的对话框通常只需要一个实例
单例模式的优缺点
优点
- 节省内存:只有一个实例,节省内存空间
- 全局访问:提供全局访问点,方便使用
- 延迟加载:可以实现延迟加载,节省资源
- 线程安全:可以实现线程安全的单例
缺点
- 违反单一职责原则:既负责创建实例又负责业务逻辑
- 难以扩展:单例类难以继承和扩展
- 难以测试:单例类难以模拟和测试
- 隐藏依赖:单例类的使用者难以发现依赖关系
单例模式的注意事项
- 构造函数私有化:防止外部直接实例化
- 线程安全:在多线程环境下保证实例唯一
- 防止反射攻击:防止通过反射创建多个实例
- 防止序列化破坏:防止序列化和反序列化破坏单例
- 延迟加载:根据需要选择是否延迟加载
实际应用示例
/**
* 日志管理器单例示例
*/
public class LoggerSingleton {
private static volatile LoggerSingleton instance;
private StringBuilder logBuffer;
private LoggerSingleton() {
logBuffer = new StringBuilder();
}
public static LoggerSingleton getInstance() {
if (instance == null) {
synchronized (LoggerSingleton.class) {
if (instance == null) {
instance = new LoggerSingleton();
}
}
}
return instance;
}
public void log(String message) {
String logEntry = "[" + new java.util.Date() + "] " + message + "\n";
logBuffer.append(logEntry);
System.out.print(logEntry);
}
public String getLogContent() {
return logBuffer.toString();
}
}
// 使用示例
public class SingletonDemo {
public static void main(String[] args) {
// 获取单例实例
LoggerSingleton logger1 = LoggerSingleton.getInstance();
LoggerSingleton logger2 = LoggerSingleton.getInstance();
// 验证是否为同一个实例
System.out.println("logger1 == logger2: " + (logger1 == logger2)); // true
// 使用单例记录日志
logger1.log("第一条日志");
logger2.log("第二条日志");
logger1.log("第三条日志");
// 输出日志内容
System.out.println("日志内容:");
System.out.println(logger1.getLogContent());
}
}
总结
单例模式是设计模式中最常用的模式之一,它保证一个类只有一个实例,并提供一个全局访问点。在实际开发中,我们应该根据具体需求选择合适的单例实现方式:
- 一般情况下:推荐使用静态内部类或双重检查锁定
- 需要防止反射和序列化破坏:推荐使用枚举单例
- 简单场景:可以使用饿汉式
- 性能要求高:避免使用同步方法的懒汉式
记住,单例模式虽然简单,但在实际使用中需要注意线程安全、反射攻击、序列化破坏等问题。