目录
一、单例模式
1.1 单例模式的概念
单例模式是⼀种创建型设计模式, 它的核⼼思想是保证⼀个类只有⼀个实例,并提供⼀个全局访问点来访问这个实例。
-
只有⼀个实例的意思是,在整个应⽤程序中,只存在该类的⼀个实例对象,⽽不是创建多个相同类型的对象。
-
全局访问点的意思是,为了让其他类能够获取到这个唯⼀实例,该类提供了⼀个全局访问点(通常是⼀个静态⽅法),通过这个⽅法就能获得实例。
1.1.1 为什么要使⽤单例设计模式
单例模式的优点如下:
-
全局控制:保证只有⼀个实例,这样就可以严格的控制客户怎样访问它以及何时访问它,简单的说就是对唯⼀实例的受控访问(引⽤⾃《⼤话设计模式》第21章)
-
节省资源:也正是因为只有⼀个实例存在,就避免多次创建了相同的对象,从⽽节省了系统资源,⽽且多个模块还可以通过单例实例共享数据。
-
懒加载:单例模式可以实现懒加载,只有在需要时才进⾏实例化,这⽆疑会提⾼程序的性能。
1.1.2 单例模式的基本要求
-
私有的构造函数:防⽌外部代码直接创建类的实例
-
私有的静态实例变量:保存该类的唯⼀实例
-
公有的静态⽅法:通过公有的静态⽅法来获取类的实例
1.2 单例设计模式的实现
1.2.1 懒汉式
懒汉式指的是只有在请求实例时才会创建,如果在⾸次请求时还没有创建,就创建⼀个新的实例,如果已经创建,就返回已有的实例,意思就是需要使⽤了再创建,所以称为“懒汉”。
在多线程环境下,由于饿汉式在程序启动阶段就完成了实例的初始化,因此不存在多个线程同时尝试初始化实例的问题,但是懒汉式中多个线程同时访问 getInstance() ⽅法,并且在同⼀时刻检测到实例没有被创建,就可能会同时创建实例,从⽽导致多个实例被创建,这种情况下我们可以采⽤⼀些同步机制,例如使⽤互斥锁来确保在任何时刻只有⼀个线程能够执⾏实例的创建。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 私有构造⽅法,防⽌外部实例化
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
懒汉模式使用的是双重效验锁和 volatile 来保证线程安全的,从上述代码可以看出,无论是饿汉模式还是懒汉模式,它们的实现步骤都是一样的:
-
创建一个私有的构造方法,防止其他调用的地方直接 new 对象,这样创建出来的对象就不是单例对象了。
-
创建一个私有变量来保存单例对象。
-
提供一个公共的方法返回单例对象。
懒汉模式相比于饿汉模式来说,不会造成资源的浪费,但写法要复杂一些。
1.2.2 饿汉式
饿汉模式也叫预加载模式,它是在类加载时直接创建并初始化单例对象,所以它并不存在线程安全的问题。它是依靠 ClassLoader 类机制,在程序启动时只加载一次,因此不存在线程安全问题,它的实现代码如下:
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
// 私有构造⽅法,防⽌外部实例化
}
public static Singleton getInstance() {
return instance;
}
}
优点:实现简单、不存在线程安全问题。
缺点:类加载时就创建了对象,创建之后如果没被使用,就造成了资源浪费的情况。
1.2.3 静态内部类
静态内部类既能保证线程安全,又能保证懒加载,它只有在被调用时,才会通过 ClassLoader 机制来加载和初始化内部静态类,因此它是线程安全的,此模式的实现代码如下:
public class Singleton {
// 1.防止外部直接 new 对象破坏单例模式
private Singleton() {
}
// 2.静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 3.提供公共获取单例对象的方法
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
1.3 单例模式的适用场景
-
资源共享:多个模块共享某个资源的时候,可以使⽤单例模式,⽐如说应⽤程序需要⼀个全局的配置管理器来存储和管理配置信息、亦或是使⽤单例模式管理数据库连接池。
-
只有⼀个实例:当系统中某个类只需要⼀个实例来协调⾏为的时候,可以考虑使⽤单例模式, ⽐如说管理应⽤程序中的缓存,确保只有⼀个缓存实例,避免重复的缓存创建和管理,或者使⽤单例模式来创建和管理线程池。
-
懒加载:如果对象创建本身就⽐较消耗资源,⽽且可能在整个程序中都不⼀定会使⽤,可以使⽤单例模式实现懒加载。
1.4 典型应用场景
-
单例模式的典型使用场景是 Spring 中 Bean 的实例,Bean 实例默认使用的就是单例模式。
-
单例模式的使用场景还有数据库连接池、Redis 连接池、线程池等使用场景。