基本概念

单件模式:一种创建型设计模式,确保一个类在系统中只有一个实例,并提供全局访问点。主要用于解决全局资源共享的问题,常用于需要唯一控制的场景(如配置管理、日志记录、线程池等)。

核心结构

  1. 类自身负责创建唯一实例
  2. 提供全局访问该实例的方法
  3. 确保实例只能被创建一次

示例说明

场景:设计一个全局日志记录器,确保系统中只有一个日志实例

  1. 饿汉式单例(线程安全,立即加载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

// 饿汉式单例(线程安全,类加载时创建实例)
public class LoggerHungry {
// 1. 私有静态成员变量,类加载时立即初始化
private static final LoggerHungry instance = new LoggerHungry();

// 2. 私有构造函数,防止外部实例化
private LoggerHungry() {
System.out.println("日志记录器初始化");
}

// 3. 公共静态方法,提供全局访问点
public static LoggerHungry getInstance() {
return instance;
}

public void log(String message) {
System.out.println("日志记录: " + message);
}
}

  1. 懒汉式单例(延迟加载,需手动处理线程安全)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

// 懒汉式单例(延迟加载,需同步保证线程安全)
public class LoggerLazy {
private static LoggerLazy instance;

private LoggerLazy() {}

// 简单同步(性能开销大,每次调用都加锁)
public static synchronized LoggerLazy getInstance() {
if (instance == null) {
instance = new LoggerLazy();
}
return instance;
}

// 优化:双重检查锁(DCL)
public static LoggerLazy getInstanceDCL() {
if (instance == null) { // 第一重检查,避免无意义的锁竞争
synchronized (LoggerLazy.class) {
if (instance == null) { // 第二重检查,避免多线程重复创建
instance = new LoggerLazy();
}
}
}
return instance;
}

public void log(String message) {
System.out.println("日志记录: " + message);
}
}

  1. 静态内部类单例(推荐,兼顾延迟加载和线程安全)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

// 静态内部类单例(推荐实现方式)
public class LoggerStaticInner {
// 私有构造函数
private LoggerStaticInner() {}

// 静态内部类,仅在首次调用getInstance时加载
private static class InstanceHolder {
private static final LoggerStaticInner INSTANCE = new LoggerStaticInner();
}

// 访问方法,无需同步
public static LoggerStaticInner getInstance() {
return InstanceHolder.INSTANCE;
}

public void log(String message) {
System.out.println("日志记录: " + message);
}
}

  1. 枚举单例(最简捷,天然支持序列化和反序列化)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// 枚举单例(Java特有的实现方式)
public enum LoggerEnum {
INSTANCE; // 枚举常量本身就是单例实例

public void log(String message) {
System.out.println("日志记录: " + message);
}
}
// 客户端调用示例
public class Client {
public static void main(String[] args) {
// 饿汉式
LoggerHungry hungry1 = LoggerHungry.getInstance();
LoggerHungry hungry2 = LoggerHungry.getInstance();
System.out.println("饿汉式实例是否相同: " + (hungry1 == hungry2));

// 静态内部类式
LoggerStaticInner inner1 = LoggerStaticInner.getInstance();
LoggerStaticInner inner2 = LoggerStaticInner.getInstance();
System.out.println("静态内部类实例是否相同: " + (inner1 == inner2));

// 枚举式
LoggerEnum enum1 = LoggerEnum.INSTANCE;
LoggerEnum enum2 = LoggerEnum.INSTANCE;
System.out.println("枚举式实例是否相同: " + (enum1 == enum2));
}
}

  1. 执行结果
1
2
3
4
5
6

日志记录器初始化
饿汉式实例是否相同: true
静态内部类实例是否相同: true
枚举式实例是否相同: true

类图如下:

单例模式

总结

优点:

  • 全局唯一:确保系统中只有一个实例,避免资源重复创建
  • 方便访问:提供全局访问点,简化代码设计
  • 节约资源:适合管理共享资源(如数据库连接池、线程池)

缺点:

  • 违反单一职责:单例类可能承担创建实例和业务逻辑的双重职责
  • 不利于测试:单例模式难以模拟不同实例场景,增加单元测试复杂度
  • 潜在线程安全问题:非线程安全的实现需要额外处理同步
  • 可能导致内存泄漏:长期持有资源可能引发内存问题

应用场景

  • 全局资源管理:日志记录器(java.util.LogManager),配置管理器(读取系统配置),线程池(统一管理线程资源)
  • 系统控制:Windows任务管理器(确保只有一个实例),打印机后台处理程序,数据库连接池(避免创建过多连接)
  • 框架设计:Spring框架中的ApplicationContext(单例作用域),HibernateSessionFactory
  • JDK中的应用:java.lang.Runtime:获取当前运行时环境,全局唯一,java.util.LogManager:日志系统的管理类,javax.servlet.ServletContext:Web应用的全局上下文

注意

  • 序列化问题:单例类若实现Serializable接口,需添加readResolve()方法防止反序列化时创建新实例。
  • 反射攻击:私有构造函数无法完全阻止反射创建实例,需在构造函数中添加判断
  • 多类加载器:不同类加载器可能创建多个实例,需特殊处理
  • 推荐实现:优先使用静态内部类单例或枚举单例,兼顾线程安全和性能
  • 与工厂模式相比:关注对象创建,单例模式关注对象唯一性