一、什么是单例模式
单例模式是一种罕用的软件设计模式,其定义是单例对象的类只能容许一个实例存在。该类负责创立本人的对象,同时确保只有一个对象被创立。个别罕用在工具类的实现或创建对象须要耗费资源的业务场景。
单例模式的特点:
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种单例模式,各有优缺点,动态外部类是所有单例模式中最举荐的模式。
如果您看了这篇文章感觉有所播种,帮忙关注一下我的公众账号:苏三说技术。原创不易,你们的激励是我保持写作最大的能源,谢谢大家。