单例模式 之 写法大全
单例模式不仅仅是面试官最爱考核的内容之一,也是程序员们在实际开发中经常用到的设计模式之一,所以它是我们的必备技能之一。说到单例模式,你能想到哪些问题呢?
-
单例模式的特点,请说说?
-
单例模式,主要有哪些应用场景,你还记得吗?
-
单例模式有哪些写法,你还记得多少?
-
单例模式是线程安全的吗?
先上一张思维导图:
图片单例模式:是一种创建型模式,不为别人创建对象,只为自己创建对象,以提供一个全局的唯一的对象实例。这个全局是指在一个容器里的全局。一个启动的项目只保持一个该对象的实例。
使用场景:生产唯一序列号,Web里的计算器,还要其他比较消耗资源的开销,比如数据库连接池,I/O操作等等。
缺点:没有接口,不能继承,与单一职责原则冲突。
背诵再多概念,也不及敲上一段代码。
1. 单例模式 之 懒汉模式一,线程不安全;
/**
* 懒汉式 ,线程不安全
* 延迟加载
* @author 程就人生
* @Date 2022/1/25
*/
public class Singleton {
/**
* 静态的实例变量,实例化后不会被改变
*/
private static Singleton instance;
/**
* 私有的构造函数
*/
private Singleton (){}
/**
* 没加同步锁,不支持多线程,线程不安全
* @return
*/
public static Singleton getInstance() {
// 延迟加载
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
图片
懒汉模式有两种,一种是线程不安全的,一种是线程安全的。
2. 单例模式 之 懒汉模式二,线程安全;
该实现方式,增加了同步锁,但会对性能有影响。
/**
* 懒汉式 ,线程安全
* 延迟加载
* @author 程就人生
* @Date
*/
public class Singleton {
/**
* 静态的实例变量,实例化后不会被改变
*/
private static Singleton instance;
/**
* 私有的构造函数
*/
private Singleton (){}
/**
* 使用了synchronized 关键字,线程安全,支持多线程
* @return
*/
public static synchronized Singleton getInstance() {
// 延迟加载
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. 单例模式 之 饿汉模式一;
图片/**
* 饿汉式 1
* 线程安全,非延迟加载
* 不管是否使用,先进行了实例化,所以称之为饿汉式
* @author 程就人生
* @Date 2022/1/25
*/
public class Singleton {
/**
* 先静态,后动态
* 先属性,后方法
* 先上后下
*/
private static Singleton instance = new Singleton();
/**
* 私有的构造函数
*/
private Singleton (){}
/**
* 直接返回
* @return
*/
public static Singleton getInstance() {
return instance;
}
}
4. 单例模式 之 饿汉模式二;
/**
* 饿汉式 静态块单例模式
* 线程安全,非延迟加载
* @author 程就人生
* @Date 2022/1/25
*/
public class Singleton{
private static final Singleton instance;
/**
* 静态代码块,公共内存区域
*/
static {
instance = new Singleton();
}
/**
* 私有的构造函数
*/
private Singleton (){}
/**
* 直接返回
* @return
*/
public static Singleton getInstance() {
return instance;
}
}
5. 单例模式 之 双检锁/双重校验锁 (DCL double-checked locking)
该实现方式,是对懒汉安全模式的升级,提高了性能。
图片/**
* 双检锁/双重校验锁 (DCL double-checked locking)
* 线程安全,延迟加载
* @author 程就人生
* @Date 2022/1/25
*/
public class Singleton {
/**
* 静态的实例变量,实例化后不会被改变
* 还使用了 volatile 关键字
*/
private volatile static Singleton singleton;
/**
* 私有的构造函数
*/
private Singleton (){}
/**
* 在获取实例的方法内部使用了同步锁
* 同步锁是在getInstance方法内,阻塞发生在getInstance方法上,而不是类上;
* 业务逻辑不复杂,用户感知不到
* @return
*/
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
// new一个对象做的三件事
// 1.分配内存给对象
// 2.初始化对象
// 3.设置singleton指向刚分配的内存地址
singleton = new Singleton();
}
}
}
return singleton;
}
}
6. 单例模式 之 登记式/静态内部类;
该实现方式,是对 双检锁/双重校验锁 方式的升级;兼顾了饿汉式单例模式的内存浪费和同步锁的性能问题。
图片
/**
* 登记式/静态内部类
* 线程安全,延迟加载
* 兼顾饿汉单例模式的内存浪费问题和同步的性能问题
* @author 程就人生
* @Date 2022/1/25
*/
public class Singleton {
/**
* 私有的构造函数
* 使用Singleton的时候,默认会先初始化内部类
*/
private Singleton (){}
/**
* 获取实例
* static 使单例的空间共享,保证这个方法不会被重写、重载
* @return
*/
public static final Singleton getInstance() {
// 返回结果以前,一定会先调用加载内部类
return SingletonHolder.INSTANCE;
}
/**
* 静态内部类,默认不加载
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
7. 单例模式 之 枚举式;
大师们都推崇的写法,最简易的写法。
图片
/**
* 枚举式
* 线程安全,非延迟加载
* @author 程就人生
* @Date 2022/1/25
*/
public enum Singleton {
INSTANCE;
public static Singleton getInstance() {
return INSTANCE;
}
}
这些单例模式真的是安全的吗,口说无凭,来一个简单的多线程验证一下吧:
/**
* 一个简单的多线程
* 用多线程测试单例模式的线程安全
* @author 程就人生
* @Date
*/
class Thread1 extends Thread{
// 线程名称
private String name;
public Thread1(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(name + "运行 : " + i);
// 输出单例模式对象的地址
System.out.println(Singleton.getInstance().toString());
try {
//随机休眠
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在main方法里调用;
public static void main(String[] argo){
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
}
- 单例模式 之 懒汉模式一,线程不安全,测试结果:
A运行 : 0
B运行 : 0
com.example.Singleton@b9098ae
com.example.Singleton@12b0f150
B运行 : 1
com.example.Singleton@b9098ae
B运行 : 2
com.example.Singleton@b9098ae
A运行 : 1
com.example.Singleton@b9098ae
A运行 : 2
com.example.Singleton@b9098ae
第二个地址和其他地址不一样,没有实现实例单一,因此是线程不安全的。
2. 单例模式 之 懒汉模式二,线程安全,测试结果:
A运行 : 0
B运行 : 0
com.example.Singleton@790d3283
com.example.Singleton@790d3283
B运行 : 1
A运行 : 1
com.example.Singleton@790d3283
A运行 : 2
com.example.Singleton@790d3283
com.example.Singleton@790d3283
B运行 : 2
com.example.Singleton@790d3283
查看输出的实例地址都是一样的,保证了单一实例,因此是线程安全的。
其他的就不一一验证了,请读者自行验证。