名称
单例模式(SINGLETON)
目的
保证一个类仅有一个实例,并提供一个访问它的全局访问点
适用性
在下面的情况下可以使用Singleton模式
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
结构
单例(Singleton):
定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(在java中的意思就是静态方法)。
可能负责创建它自己的唯一实例。
协作
客户只能通过Singleton的Instance操作访问一个Singleton的实例。
效果
优点
对唯一实例的受控访问。因为Singleton类封装它的唯一实例,所以它可以严格的控制客户怎样和何时访问它。
缩小名空间,节约资源。Singleton模式是对全局变量的一种改进,它避免了那些存储唯一实例的全局变量污染名空间。
允许对操作和表示的精化。Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。可以用所需要的类的实例在运行时刻配置应用,使得配置更加方便。
允许可变数目的实例。允许Singleton类有多个实例,此外,还可以用相同的方法来控制应用所使用的实例的数目,只需改变允许访问Singleton实例的操作即可。
缺点
违反单一职责原则。单例类往往承担了过多的责任,这与单一职责原则相违背,可能会导致类过于庞大且难以维护。
隐藏依赖关系。单例模式可能隐藏了类之间的依赖关系,这会导致代码的可读性和可测试性降低。
难以调试。因为单例模式中的实例在整个应用程序生命周期中都是唯一的,所以如果实例的状态被意外修改,很难追踪到问题的根源。
限制灵活性。如果需要更改单例的行为,可能需要修改大量的代码。此外,如果需要在不同的环境或条件下有不同的行为,则单例模式会显得不够灵活。
不利于并行开发。单例模式可能会导致模块间的耦合度增加,这会使得并行开发变得更加困难。
测试困难。单例模式通常会使单元测试变得更加复杂,因为单例的实例在整个测试过程中都存在,难以模拟和隔离。
应用
问题
假设我们要获取一个独一无二的总统,那么在对总统实例化的时候只能有且仅有一个实例,此时我们需要隐藏其他构造总统实例的方法(构造),提供唯一的方法去获取总统实例。
示例
UML
代码示例
单例:总统(President),隐藏构造方法,提供唯一获取实例的方法,并且处理多线程问题
package com.ysj.part3.singleton.singleton;
public class President {
private static volatile President instance = null; //保证instance在所有线程中同步
//private避免类在外部被实例化
private President() {
System.out.println("产生一个总统!");
}
/**
* 这里为了保证线程(避免多线程情况下创建多余的实例)安全加了synchronized
* 还有其他的方式来保证单例,例如:1 直接初始化静态变量 2用“双重检查加锁”,在getInstance()中减少使用同步
* @return
*/
public static synchronized President getInstance() {
//在getInstance方法上加同步
if (instance == null) {
instance = new President();
} else {
System.out.println("已经有一个总统,不能产生新总统!");
}
return instance;
}
public void getName() {
System.out.println("我是美国总统:特朗普。");
}
}
客户端:调用方,使用CountDownLatch 保证两个线程同时执行President.getInstance()
package com.ysj.part3.singleton;
import com.ysj.part3.singleton.singleton.President;
import lombok.SneakyThrows;
import java.util.concurrent.CountDownLatch;
/**
* 单例模式
* 保证一个类仅有一个实例,并提供一个访问它的全局访问点
* 参考博客:https://blog.csdn.net/GJ_007/article/details/123874405
*/
public class Client {
//用来保证线程同时执行,可以在《java并发编程的艺术》一书中找到CountDownLatch的用法
static CountDownLatch startCountDownLatch = new CountDownLatch(1);
static CountDownLatch endCountDownLatch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
final President[] zt = new President[2];
new Thread(new Runnable() {
@SneakyThrows
public void run() {
startCountDownLatch.await();
zt[0] = President.getInstance();
zt[0].getName(); //输出总统的名字
endCountDownLatch.countDown();
}
}).start();
new Thread(new Runnable() {
@SneakyThrows
public void run() {
startCountDownLatch.await();
zt[1] = President.getInstance();
zt[1].getName(); //输出总统的名字
endCountDownLatch.countDown();
}
}).start();
startCountDownLatch.countDown();
endCountDownLatch.await();
if (zt[0] == zt[1]) System.out.println("他们是同一人!");
}
}
已知应用
Spring Framework
Spring 框架中的 Bean 默认就是单例模式。这意味着每个定义的 Bean 在 Spring 容器中只有一个实例,无论有多少个类引用它。
Hibernate SessionFactory
Hibernate 是一个流行的 Java ORM 框架,其中 SessionFactory 类通常以单例模式使用。在整个应用中,一般只创建一个 SessionFactory 实例,用于创建 Session 对象。
Log4j Logger
Log4j 中的 Logger 类也是通过单例模式来实现的。每个类都有一个与之关联的 Logger 实例,这些实例在整个应用中是唯一的。
JDBC DataSource
在数据库连接池中,如 HikariCP 或 DBCP,DataSource 通常以单例形式存在,以确保所有数据库操作都使用同一个连接池。
ThreadLocal
虽然不是传统的单例模式,ThreadLocal 类提供了一种线程级别的单例模式。每个线程都有自己的独立副本,从而避免了线程安全问题。
Spring Security Context
Spring Security 使用 SecurityContextHolder 来存储当前登录用户的信息。这个上下文通常在整个应用中是单例的。
Spring MVC DispatcherServlet
在 Spring MVC 中,DispatcherServlet 通常配置为单例模式,以便在整个 Web 应用中处理所有的 HTTP 请求。
Quartz Scheduler
Quartz 是一个任务调度框架,其中 Scheduler 接口的实现通常是单例的,负责管理和触发定时任务。
Apache Commons Configuration
Apache Commons Configuration 提供了配置文件管理的功能,其中 Configuration 对象可以作为单例使用,便于整个应用中统一管理配置信息。
评论区