单例设计模式
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点
保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象
为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。
实现步骤:
所有单例的实现都包含以下两个相同的步骤:
将默认构造函数设为私有, 防止其他对象使用单例类的
new
运算符。新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象
伪代码
public final class Singleton {
private static Singleton instance;
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
单例模式优缺点
优点:
你可以保证一个类只有一个实例
你获得了一个指向该实例的全局访问节点
仅在首次请求单例对象时对其进行初始化
缺点:
违反了单一职责原则, 该模式同时解决了两个问题
单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等
该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象
单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式
只保证创建一个对象,为什么还要避免多线程创建对象
重复创建对象:多个线程同时通过单例模式创建对象时,可能会导致多次实例化,破坏了单例模式的初衷。
状态不一致:如果单例对象的创建过程中涉及到一些状态的设置或初始化操作,多个线程同时进行创建可能会导致状态不一致,使得对象处于不可预测的状态。
资源竞争:如果单例对象的创建涉及到一些共享资源或者文件操作等,多个线程同时创建可能会导致资源竞争和冲突,进而造成程序崩溃或数据损坏等问题
对于第一点可以考虑以下情况:
线程 A 和线程 B 同时检查到实例对象不存在。
线程 A 先获取到锁,开始创建实例对象。
线程 A 创建完实例对象后释放锁。
线程 B 获取到锁,然后也创建实例对象。
这样就导致了两个实例对象的创建
这就是多线程环境下可能出现的问题。为了解决这个问题,需要在创建实例对象的过程中引入同步机制,确保只有一个线程可以执行创建操作。常用的方法包括使用双重检查锁、静态内部类等,这些方法可以有效地保证在多线程环境下只创建一个实例对象
单例模式在java中的应用场景
Spring Bean管理
在Spring框架中,当我们声明一个Bean时,默认情况下是单例的。这意味着Spring容器会管理Bean的生命周期,并确保在整个应用程序中只有一个实例
@Component
public class MySingletonBean {
// Class definition
}
线程池
在多线程环境下,线程池是一种常见的单例对象。它负责管理可用的线程,并提供给需要执行任务的线程
ExecutorService executorService=Executors.newFixedThreadPool(10);
日志对象
在Java应用中,通常会使用单例模式来管理日志记录器对象,以确保在整个应用程序中只有一个日志实例
public class Logger {
private static Logger instance;
private Logger() {
// private constructor to prevent instantiation
}
public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
public void log(String message) {
// log message
}
}
数据库连接池
在使用数据库时,可以使用单例模式来管理数据库连接池,以确保在应用程序中只有一个连接池实例,避免资源浪费和性能问题
public class DatabaseConnectionPool {
private static DatabaseConnectionPool instance;
private DatabaseConnectionPool() {
// private constructor to prevent instantiation
}
public static DatabaseConnectionPool getInstance() {
if (instance == null) {
instance = new DatabaseConnectionPool();
}
return instance;
}
// Other methods for managing database connections
}