单例模式

本文最后更新于 2025年7月25日 凌晨

单例模式 是一种常见的软件设计模式,其定义是单例对象的类只能允许有一个实例存在。

适用场景

在面向对象系统的业务处理过程中,通常涉及多个对象之间的协作。根据其职责与调用特征,我们可以将其大致分为两类:

  1. 事务描述者(以状态变更为主) :主要负责维护核心业务状态,如订单状态、用户会话等,它们持有可变字段,反映业务流程的演进;
  2. 过程处理者(以调用与中间处理为主) :如服务对象、控制器等,侧重执行逻辑与临时数据的处理,其本身状态通常较少或为无状态。

根据 JVM 的内存模型,对象的方法定义(包括字节码)存储在“方法区(或元空间)“中,为所有实例共享,且为只读结构。因此,对于无状态或轻状态的过程处理对象,我们通常可以使用单例模式进行复用,因为它们的方法是线程安全的,只要不引入共享可变状态,就不会产生线程安全问题。

另一类是用于访问底层资源的 资源访问对象 (如数据库连接、文件句柄、Socket 等),这类对象封装了对外部资源的引用和生命周期管理,具备一定的重量级。在实际使用中,我们通常通过连接池等方式创建有限数量的资源对象,结合对象池化复用机制来降低频繁创建/释放资源的性能开销。

总结来说:

  • 方法只读 → 可共享 → 适合单例模式
  • 资源昂贵 → 应复用 → 适合连接池机制

优点

1 内存中只有一个对象,节省内存空间:在内存中只存在一个对象,不会频繁地创建对象,节省了内存空间

2 避免频繁地创建,销毁对象,可以提高性能:对象只初始化一次,在调用的时候,无需重复进行对象的创建,提高了程序的性能

3 避免对共享资源的多重占用:比如写文件操作

4 全局访问:在程序的任何一个地方都能进行调用,并且是同一个对象。

缺点

1 不适用于变化的对象,如果是同一类型的对象总是在不同的用例场景发生变化,单例就会引起数据错误,不能保存彼此的状态

2 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难

3 单例类的职责过重,在一定程度上违背了”单一职责原则“:要负责对象本身的职责,还需要负责对象的创建

4 滥用单例将带来一些负面问题,如为了节省资源,将数据库连接池对象设计为单例,可能会导致共享连接池对象的程序过多,而出现连接池溢出;如果实例化的对象长时间不被利用,会被认为是垃圾而被系统回收,这将导致对象状态的丢失

结构实现

1
2
3
4
5
6
7
8
------------------------------
| Singleton |
------------------------------
| - instance: Singleton |
------------------------------
| - Singleton() |
| + getInstance(): Singleton |
------------------------------

实现

懒汉模式

懒汉模式即在首次被请求时,才会完成对象的创建。优点是节省内存空间和应用启动时间,缺点则是在创建时,可能存在线程安全问题。

创建过程

  1. 构造方法私有化
1
2
3
private Executor(){

}
  1. 提供对象创建方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Executor{
private static volatile Executor executor ;
// 定义私有化实例对象,使用volatile提示编译器不要修改执行过程
private Executor(){};
// 私有化实例构造器
public static Executor getInstance(){
if(executor == null){
// 如果私有化实例不存在,则创建实例
executor = new Executor();
}
return executor ;
// 最终返回创建后的唯一实例
}
}

线程安全问题

根据代码,我们可以看到处理过程主要是一个实例的对象创建过程,如果实例已经被创建,则返回实例。这样在未创建时,就存在一个临界区,即静态方法被同时调用,临界区内不存在已创建的对象,这时就会有多个线程进行私有实例的创建,进而导致创建了多个实例。

解决方案

  • 使用synchronized同步代码块
1
2
3
4
5
6
public synchronized static Executor getInstance(){
if(instance == null){
instance = new Executor();
}
return instance ;
}

但是 线程安全问题仅存在于实例创建阶段,当实例创建后,每次请求再涉及到线程安全检查则会销毁不必要的资源。则可以使用 多次检查+synchronized 的方式进行创建过程

1
2
3
4
5
6
7
8
9
10
public static Executor getInstance(){
if(instance == null){
synchronized(GirlFriend.class){
if(executor == null){
executor= new Executor();
}
}
}
return executor
}

饿汉模式

饿汉模式即在类加载阶段完成了对单例的创建,在后续请求中直接返回该单例对象,优点在于不存在线程安全问题,缺点则在于如果对象加载过程资源消耗很大,则会影响应用整体的启动时间。

创建过程

1
2
3
4
5
6
7
public class Executor {
private static Executor executor = new executor();
public static getInstance(){
return executor ;
}

}

静态内部类

1
2
3
4
5
6
7
8
9
public class Executor {
private static class InnerClassHolder(){
private static Executor executor = new Executor();
}

public static Executor getInstance(){
return InnerClassHolder.instance ;
}
}

枚举类实现

通过定义单枚举类对象,创建单一实例

1
2
3
public enum Executor {
EXECUTOR ;
}

单例模式的线程池实现

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
public class ThreadPool {
private volatile static ThreadPool threadPool ;
private ThreadPoolExecutor threadPoolExecutor = null ;
private final int THREAD_COUNT = 50 ;

private ThreadPool(){
threadPoolExecutor = (ThreadPoolExecutor)Executors.newFixedThreadPool(THREAD_COUNT);
}

public void execute(Thread t){
if(!threadPoolExecutor.isShutdown()){
threadPoolExecutor.execute(t);
}
}

public static ThreadPool getInstance(){
if(threadPool == null){
synchronized(ThreadPool.class){
if(threadPool == null){
threadPool = new ThreadPool();
}
}
}
return threadPool ;
}
}

单例模式
http://gadoid.io/2025/07/23/单例模式/
作者
Codfish
发布于
2025年7月23日
许可协议