创建者模式-单例模式
# 概念
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
# 使用场景
- 要求生成唯一序列号的环境。
- 在整个项目中需要一个共享访问点或共享数据,例如一个 Web 页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的。
- 创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源。
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为 static 的方式)。
# 代码示例
# 线程安全
public class Singleton {
private static final Singleton singleton = new Singleton();
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
//类中其他方法,尽量是 static
public static void doSomething(){
}
}
# 线程不安全
public class Singleton {
private static Singleton singleton = null;
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
针对线程不安全
在 getSingleton 方法前加 synchronized
关键字,也可以在 getSingleton 方法内增加 synchronized
来实现。
# JDK:Runtime.class
在 JDK 中的 Runtime.class 就使用到了此方式的单例模式。
private static Runtime currentRuntime = new Runtime();
# Spring:ReactiveAdapterRegistry.class
使用了 synchronized 代码块 + 双重校验 + volatile 的懒汉式单例模式
@Nullable
private static volatile ReactiveAdapterRegistry sharedInstance;
public static ReactiveAdapterRegistry getSharedInstance() {
ReactiveAdapterRegistry registry = sharedInstance;
if (registry == null) {
synchronized (ReactiveAdapterRegistry.class) {
registry = sharedInstance;
if (registry == null) {
registry = new ReactiveAdapterRegistry();
sharedInstance = registry;
}
}
}
return registry;
}
# Tomcat:TomcatURLStreamHandlerFactory.class
使用了 synchronized 代码块 + 双重校验 + volatile 的懒汉式单例模式
private static volatile TomcatURLStreamHandlerFactory instance = null;
private static TomcatURLStreamHandlerFactory getInstanceInternal(boolean register) {
// Double checked locking. OK because instance is volatile.
if (instance == null) {
synchronized (TomcatURLStreamHandlerFactory.class) {
if (instance == null) {
instance = new TomcatURLStreamHandlerFactory(register);
}
}
}
return instance;
}
# 知识点
# 懒汉式
在首次调用时创建对象,但需要注意线程安全问题。
解决线程安全的方法:
# synchronized 方法
在 getSingleton()
方法上加锁( synchronized
)
public synchronized static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
缺点:锁的粒度过大,导致整个方法,同时只能有一个线程访问。
# synchronized 代码块 + 双重校验 + volatile
private volatile static Singleton singleton;
public static Singleton getSingleton(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
在类加载时可能出现重排序,先执行赋值 singleton
,再初始化空间,从而导致其他线程获取时导致空指针或未知问题。可使用 volatile
关键字避免重排序。
参考:3. 类加载过程讲解_哔哩哔哩_bilibili (opens new window)
# 内部类(延迟加载)
class InnerClassSingleton{
private static class SingletonHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance(){
return SingletonHolder.instance;
}
}
实例会在首次调用 getInstance()
时创建。
# 饿汉式
# 静态变量
private static final Singleton singleton = new Singleton();
只有在真正主动使用对应的类时,才会触发初始化:
- 当前类是启动类即 main 函数所在类
- 直接进行 new 操作
- 访问静态属性
- 访问静态方法
- 用反射访问类
- 初始化一个类的子类
- 等等
类加载的初始化阶段就完成了实例的初始化。本质上就是借助 jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM 以同步的形式来完成类加载的整个过程)。
# 枚举
通过反编译可知,实际上是使用 static 代码块初始化的饿汉式单例模式
# 总结
在被问到设计模式的时候,相信大多数刚接触设计模式的同学,第一个相对的就是单例模式,也可能跟几乎所有的教程第一个设计模式都是讲的单例模式有关。
单例设计模式的思想一句话就是一个类只有一个实例化对象,这些类对象是可以复用的,一个和多个是没有区别的,因此出于节省资源来考虑,使用单例模式更佳。单例模式中的实例化过程,可分为主动(饿汉式)、被动(懒汉式)两种,对性能有较高要求的,被动的懒汉式会更好,只有在调用此对象方法时才会被实例化,但需要处理好线程安全问题。在 Spring 和 Tomcat 的源码中使用到的单例模式,采用 synchronized代码块 + 双重校验 + volatile
的方式,因此在实际开发中使用此方式是一个不错的选择,当然如果这个对象在项目运行周期中,一定会使用的,使用主动的饿汉式创建才也不错的选择(例如 JDK 中的 Runtime.class)。