关于java:你确定你的单例模式真的用对了

25次阅读

共计 2380 个字符,预计需要花费 6 分钟才能阅读完成。

一、什么是单例模式

单例模式是一种罕用的软件设计模式,其定义是单例对象的类只能容许一个实例存在。该类负责创立本人的对象,同时确保只有一个对象被创立。个别罕用在工具类的实现或创建对象须要耗费资源的业务场景。

单例模式的特点

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 种单例模式,各有优缺点, 动态外部类是所有单例模式中最举荐的模式。

如果您看了这篇文章感觉有所播种,帮忙关注一下我的公众账号:苏三说技术。原创不易,你们的激励是我保持写作最大的能源,谢谢大家。

正文完
 0