一、单例模式介绍
单例模式(Singleton Design Pattern)定义:在同一个环境下一个类只有唯一的实例。比如:spring IOC 容器中的唯一实例,或者 JVM 中的唯一实例。
只要符合该定义的代码模型,都能够称之为单例模式。
单例模式有两大实现方式分别是:饿汉模式和懒汉模式。
无论是饿汉亦或者懒汉实现的单例,都需要关注以下几点
1、私有化构造函数,避免外部使用 new 关键实例化对象
2、单例通常是一个共享对象,所以需要考虑线程安全问题
3、单例获取时的性能问题(线程安全的解决通常会有锁的身影)
4、实例化的时机(饿汉在项目初始化时就实例化了,懒汉在第一次调用的时候才会进行初始化)
二、单例模型(使用套路)
2.1、饿汉模式
2.1.1、饿汉模式代码模板
饿汉模式就是在项目启动的时候直接进行对象的实例化。同时保证在同一环境下的单一实例。
相关模板案例如下:
public class HungrySingleton {
/** 私有化构造 **/
private HungrySingleton(){}
/** 实例化对象 **/
private static final HungrySingleton INSTANCE = new HungrySingleton();
/** 获取实例 **/
public static HungrySingleton getInstance(){
return INSTANCE;
}
}
该模板的主要三个组成部分:
1、私有化构造。防止 nwe
破坏单例。(不过闲的蛋疼同样可以破坏)
2、进行对象实例化
3、暴露获取实例对象的方法。
2.1.2、饿汉模式优点
1、在程序启动后,对象便已经初始化,在使用的时候不存在线程安全问题,不需要使用锁来保证线程安全,对比需要锁的单例模式,性能上会更好
2、假使对象的第一次实例化过程复杂耗时,饿汉模式相比较懒汉模式来说用户体验会更好,避免了某个用户请求因为实例化对象导致响应延迟的问题
3、在项目使用时初始化,在一定程度上可以提前到监测对象的运行时错误,避免在使用的时候出现不可控的错误,影响项目
2.1.3、饿汉模式的缺点
对象在启动时已经被实例化,如果实例对象占用资源多,且使用频率低,提前初始化会造成资源的浪费。
2.2、懒汉模式
懒汉模式和饿汉相反,懒汉是在第一次使用到对象,才会进行实例化操作。
而实例化操作的触发,就是对外暴露的对象获取 API 方法
因为是在第一次使用的时候才进行实例化,此时可能出现多个线程同时调用的情况发生,这就会出现线程安全问题导致出现多个实例,从而破坏单例模式,
所以懒汉模式的关键点就在实例化时的线程安全问题。而针对该问题,懒汉的实现有如下几种经典实现模型:单锁,双重检测锁,静态内部类,枚举。
2.2.1、单锁(不推荐使用)
2.2.1.1、单锁懒汉代码模板
单锁实现懒汉单例的模板如下:
public class LazyTwo {
/** 私有构造 **/
private LazyTwo(){}
/** 静态块,公共内存区域 **/
private static LazyTwo INSTANCE = null;
/** 获取实例对象 **/
public synchronized final static LazyTwo getInstance(){
if(null == INSTANCE){
INSTANCE = new LazyTwo();
}
return INSTANCE;
}
}
该模板的主要二个组成部分:
1、私有化构造。防止 nwe
破坏单例。(不过闲的蛋疼同样可以破坏)
2、暴露获取实例对象的方法。
其中最关键的就是解决实例对象获取的方法 getInstance
的线程安全问题。
如代码所示,直接通过类锁来锁定该方法,保证线程的安全
2.2.1.2、单锁懒汉优点
除了在指定场景下的懒加载功能来说,并没有啥优点。
2.2.1.3、单锁懒汉缺点
缺点很明显,每一次获取实例对象都有锁的损耗。
2.2.2、双重检测锁
2.2.2.1、双重检测锁代码模板
模板代码如下:
public class LazyThree {
/** 私有构造 **/
private LazyThree(){}
/** 静态块,公共内存区域(volatile 防止java低版本指令重排序问题) **/
private volatile static LazyThree INSTANCE = null;
/** 获取实例对象 **/
public final static LazyThree getInstance(){
// 第一次检测
if(null == INSTANCE){
// 第二次检测(类级别的锁)
synchronized (LazyThree.class){
if(null == INSTANCE){
INSTANCE = new LazyThree();
}
}
}
return INSTANCE;
}
}
该模板的主要二个组成部分:
1、私有化构造。防止 nwe
破坏单例。(不过闲的蛋疼同样可以破坏)
2、暴露获取实例对象的方法。
其中最关键的就是解决实例对象获取的方法 getInstance
的线程安全问题。
如模板代码所示,代码中进行了两次 实例是否等于null
的判断。只有在第一次实例化对象的时候,也就是需要执行写操作的时候,才存在线程安全问题,此时才进行锁操作。
一旦对象实例化完成,针对读请求,便不再有性能上的损耗。
2.2.2.2、双重检测锁优点
指定场景下的懒加载功能,对比单锁懒汉,有一定性能上的提升。
2.2.2.3、双重检测锁缺点
同样的仍然有一定的锁的损耗,不过基本上可以忽略不计。
2.2.3、静态内部类(推荐使用)
2.2.3.1、静态内部类代码模板
代码模板如下:
public class LazyFour {
/** 私有构造 **/
private LazyFour(){}
/** 静态内部类 **/
private static class LazyHolder{
/** 实例对象 **/
private static final LazyFour INSTANCE = new LazyFour();
}
/** 获取实例对象 **/
public static final LazyFour getInstance(){
return LazyHolder.INSTANCE;
}
}
该模板的主要二个组成部分:
1、私有化构造。防止 nwe
破坏单例。(不过闲的蛋疼同样可以破坏)
2、暴露获取实例对象的方法。
其中的关键点就是静态内部内,通过 JVM 机制,通过无锁的方式解决线程安全问题,性能上比加锁的会好。
2.2.4、枚举懒汉
2.2.4.1、枚举懒汉代码模板
public enum RecordRegisterEnum {
INSTANCE;
// TODO something
}
该单例模板很简单,直接声明一个枚举类即可,对于 Java 来说,每一个枚举类,其本身就是一个单例。
【公众号】花好夜猿