在软件开发中,单例设计模式(Singleton Design Pattern)是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式通常用于管理共享资源(如数据库连接池、线程池等)或需要全局唯一实例的场景。
本文将详细介绍两种常见的单例实现方式:懒汉式 和饿汉式,并分析它们的优缺点及适用场景。
1. 单例模式的核心要素
要实现单例模式,需要满足以下三个条件:
- 私有化构造方法 :防止外部通过
new
关键字创建对象。 - 提供静态方法获取唯一实例:通过一个公共的静态方法返回唯一的实例。
- 保持单一实例:确保类中只有一个实例存在。
2. 饿汉式(Eager Initialization)
饿汉式是指在类加载时就立即创建实例。这种方式的特点是简单直接,但可能会造成资源浪费(如果实例从未被使用过)。
实现代码
public class SingletonEager {
// 1. 私有化构造方法
private SingletonEager() {
System.out.println("SingletonEager instance created");
}
// 2. 在类加载时创建唯一的实例
private static final SingletonEager instance = new SingletonEager();
// 3. 提供公共的静态方法获取实例
public static SingletonEager getInstance() {
return instance;
}
}
特点
- 优点 :
- 简单易懂,实现方便。
- 线程安全(因为实例在类加载时就已经创建,不存在多线程竞争问题)。
- 缺点 :
- 如果实例从未被使用过,会浪费内存资源。
- 不适合需要延迟加载的场景。
3. 懒汉式(Lazy Initialization)
懒汉式 是指在第一次调用getInstance()
方法时才创建实例。这种方式可以避免资源浪费,但需要注意线程安全问题。
实现代码(非线程安全版本)
public class SingletonLazy {
// 1. 私有化构造方法
private SingletonLazy() {
System.out.println("SingletonLazy instance created");
}
// 2. 定义静态变量,但不立即初始化
private static SingletonLazy instance;
// 3. 提供公共的静态方法获取实例
public static SingletonLazy getInstance() {
if (instance == null) { // 第一次检查
instance = new SingletonLazy(); // 创建实例
}
return instance;
}
}
特点
- 优点 :
- 延迟加载,节省资源。
- 缺点 :
- 存在线程安全问题(多线程环境下可能创建多个实例)。
线程安全改进版(双重检查锁定)
为了解决线程安全问题,可以使用双重检查锁定(Double-Checked Locking)机制:
public class SingletonLazySafe {
// 1. 私有化构造方法
private SingletonLazySafe() {
System.out.println("SingletonLazySafe instance created");
}
// 2. 使用volatile关键字保证可见性和禁止指令重排
private static volatile SingletonLazySafe instance;
// 3. 双重检查锁定
public static SingletonLazySafe getInstance() {
if (instance == null) { // 第一次检查
synchronized (SingletonLazySafe.class) {
if (instance == null) { // 第二次检查
instance = new SingletonLazySafe();
}
}
}
return instance;
}
}
特点
- 优点 :
- 延迟加载,节省资源。
- 线程安全。
- 缺点 :
- 实现复杂度较高。
4. 对比:懒汉式 vs 饿汉式
特性 | 饿汉式 | 懒汉式 |
---|---|---|
实例创建时机 | 类加载时 | 第一次调用getInstance() 时 |
资源占用 | 可能浪费资源 | 延迟加载,节省资源 |
线程安全性 | 天然线程安全 | 需额外处理(如双重检查锁定) |
实现复杂度 | 简单 | 较复杂 |
适用场景 | 实例一定会被使用且对性能要求高 | 实例可能不会被使用或需延迟加载 |
5. 其他实现方式(扩展)
除了懒汉式和饿汉式,还有其他常见的单例实现方式,例如:
枚举单例
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("Doing something...");
}
}
- 优点:天然线程安全,防止反射攻击,简洁优雅。
- 缺点:功能有限,不适合需要继承的场景。
静态内部类
public class SingletonInnerClass {
private SingletonInnerClass() {}
private static class SingletonHolder {
private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();
}
public static SingletonInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 优点:延迟加载,线程安全,性能较好。
- 缺点:实现稍复杂。
6. 总结
- 饿汉式适合于实例一定会被使用的场景,简单高效,但可能会浪费资源。
- 懒汉式适合于实例可能不会被使用的场景,可以延迟加载,但需要注意线程安全问题。
- 如果追求简洁和安全性,推荐使用枚举单例 或静态内部类实现。