一、什么是单例模式
单例模式是一种罕用的软件设计模式,其定义是单例对象的类只能容许一个实例存在。该类负责创立本人的对象,同时确保只有一个对象被创立。个别罕用在工具类的实现或创建对象须要耗费资源的业务场景。
单例模式的特点 :
1. 类结构器公有
2. 持有本人类的援用
3. 对外提供获取实例的静态方法
咱们先用一个简略示例理解一下单例模式的用法。
public class SimpleSingleton {
打印后果:
1639705018
咱们看到两次获取 SimpleSingleton 实例的 hashCode 是一样的,阐明两次调用获取到的是同一个对象。
可能很多敌人平时工作当中都是这么用的,然而我要说的是这段代码其实是有问题的。
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
一开始就实例化对象了,如果实例化过程十分耗时,并且最初这个对象没有被应用,不是白白造成资源节约吗?
这个时候你兴许会想到,如果在真正应用的时候再实例化不就能够了?这就是我接下来要介绍的 懒汉模式 。
二、饿汉模式与懒汉模式
什么是饿汉模式?
实例在初始化的时候就曾经建好了,不论你有没有用到,都先建好了再说。益处是没有线程平安的问题,害处是节约内存空间。代码如下:
public class SimpleSingleton {
什么是懒汉模式?
顾名思义就是实例在用到的时候才去创立,“比拟懒”,用的时候才去查看有没有实例,如果有则返回,没有则新建。代码如下:
public class SimpleSingleton2 {
示例中的 INSTANCE 对象一开始是空的,在调用 getInstance 办法才会真正实例化。
如果代码可能有些敌人在应用,然而还是有问题。
有什么问题呢?
如果有多个线程中都调用了 getInstance 办法,那么都走到 if (INSTANCE == null) 判断时,可能同时成立,因为 INSTANCE 初始化时默认值是 null。这样会导致多个线程中同时创立 INSTANCE 对象,即 INSTANCE 对象被创立了屡次,违反了一个 INSTANCE 对象的初衷。
要如何改良呢?
最简略的方法就是应用 synchronized 关键字,改良后的代码如下:
public class SimpleSingleton3 {
这样总能够了吧?
不好意思,还是有问题。
有什么问题?
应用 synchronized 关键字会耗费性能,咱们应该判断 INSTANCE 为空时才加锁,而不为空不应该加锁,须要间接返回。这就须要应用双重查看锁。
饿汉模式 和 懒汉模式 各有什么优缺点?
饿汉模式 :益处是没有线程平安的问题,害处是节约内存空间。
懒汉模式 :益处是没有内存空间节约的问题,然而管制不好理论不是单例。
三、双重查看锁
双重查看锁顾名思义会查看两次,在加锁之前查看一次是否为空,加锁之后再查看一次是否为空。代码如下:
public class SimpleSingleton4 {
在加锁之前判断是否为空,能够确保 INSTANCE 不为空的状况下,不必加锁,能够间接返回。
为什么在加锁之后,还须要判断 INSTANCE 是否为空呢?
其实,是为了避免在多线程并发的状况下,比方:线程 a 和 线程 b 同时调用
getInstance,同时判断 INSTANCE 为空,则同时进行抢锁。如果线程 a 先抢到锁,开始执行 synchronized 关键字蕴含的代码,此时线程 b 处于期待状态。线程 a 创立完新实例了,开释锁了,此时线程 b 拿到锁,进入 synchronized 关键字蕴含的代码,如果没有再判断一次 INSTANCE 是否为空,则可能会反复创立实例。
不要认为这样就完了,还有问题呢?
有啥问题?
public static SimpleSingleton4 getInstance() {
getInstance 办法的这段代码,我是按 1、2、3、4、5 这种程序写的,心愿也按这个程序执行。然而 java 虚拟机实际上会有一些优化,对一些代码指令进行重排。重排之后的程序可能就变成了:1、3、2、4、5,这样在多线程的状况下同样会创立屡次实例。重排之后的代码可能如下:
public static SimpleSingleton4 getInstance() {
原来如此,那有什么方法能够解决呢?
能够在定义 INSTANCE 是加上 volatile 关键字,代码如下:
public class SimpleSingleton7 {
volatile 关键字能够保障多个线程的可见性,然而不能保障原子性。同时它也能禁止指令重排。
双重查看锁的机制既保证了线程平安,又比间接上锁进步了执行效率,还节俭了内存空间。
除了下面的单例模式之外,还有没有其余的单例模式?
四、动态外部类
动态外部类顾名思义是通过动态的外部类来实现单例模式的。
public class SimpleSingleton5 {
咱们看到在 SimpleSingleton5 类中定义了一个动态的外部类 Inner,SimpleSingleton5 类的 getInstance 办法返回的是外部类 Inner 的实例 INSTANCE。
只有第一次调用 getInstance 办法时,虚拟机才加载 Inner 并初始化 INSTANCE,只有一个线程能够取得对象的初始化锁,其余线程无奈进行初始化,保障对象的唯一性。
五、枚举
枚举是人造的单例,每一个实例只有一个对象,这是 java 底层外部机制保障的。
public enum SimpleSingleton6 {
然而理论状况下,枚举的单例用的并不多,因为它不好了解。
六、总结
本文次要介绍了:
饿汉模式、懒汉模式、双重查看锁、动态外部类 和 枚举 这 5 种单例模式,各有优缺点, 动态外部类是所有单例模式中最举荐的模式。
如果您看了这篇文章感觉有所播种,帮忙关注一下我的公众账号:苏三说技术。原创不易,你们的激励是我保持写作最大的能源,谢谢大家。