本文最后更新于 2025年7月25日 凌晨
单例模式 是一种常见的软件设计模式,其定义是单例对象的类只能允许有一个实例存在。
适用场景
在面向对象系统的业务处理过程中,通常涉及多个对象之间的协作。根据其职责与调用特征,我们可以将其大致分为两类:
- 事务描述者(以状态变更为主) :主要负责维护核心业务状态,如订单状态、用户会话等,它们持有可变字段,反映业务流程的演进;
- 过程处理者(以调用与中间处理为主) :如服务对象、控制器等,侧重执行逻辑与临时数据的处理,其本身状态通常较少或为无状态。
根据 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 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Executor{ private static volatile Executor executor ; private Executor(){}; public static Executor getInstance(){ if(executor == null){ executor = new Executor(); } return executor ; } }
|
线程安全问题
根据代码,我们可以看到处理过程主要是一个实例的对象创建过程,如果实例已经被创建,则返回实例。这样在未创建时,就存在一个临界区,即静态方法被同时调用,临界区内不存在已创建的对象,这时就会有多个线程进行私有实例的创建,进而导致创建了多个实例。
解决方案
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 ; } }
|