听说每个人都会写单例,你会了吗?
发布日期:2021-10-20 03:26:38 浏览次数:1 分类:技术文章

本文共 2823 字,大约阅读时间需要 9 分钟。

单例模式(Singleton Pattern)

当我们有这样的需求:某一些类应该只存在一个实例 的时候,我们就可以用单例模式来应对.

单例模式:确保一个类只有一个实例,并提供一个全局访问点.

单例模式是所有设计模式中最简单的一个,也是大部分人最早知道的一个设计模式.

但是即使是最简单的,也有很多可以推敲的细节,要做得对也不简单.

经典的单例

相信大家一定写过这样类似的单例模式代码:

public class Singleton {
private static Singleton ins;
private Singleton() {}
public static Singleton getIns() {
if (null == ins) {
ins = new Singleton();
}
return ins;
}}

简单总结一下这样的写法:

  1. 提供一个全局静态的getIns方法,使得易于使用.
  2. 延迟Singleton的实例化,节省资源(所谓的懒汉式).
  3. 缺点是线程不安全.当多个线程同时进入if (null == ins) {}的时候就会创建多个实例.

OK,接下来我们看来是要解决多线程不安全的问题了.

多线程安全

这个时候可能有的人就说了,这个太简单了,加一个synchronized不就结了吗?

public static synchronized Singleton getIns() {
if (null == ins) {
ins = new Singleton();
}
return ins;}

确实,增加synchronized之后能够迫使每个线程在进入这个方法之前,要先等别的线程离开该方法.也即避免了多个线程同时进入getIns方法.

诚然这个能解决问题,但是我们知道synchronized是非常耗性能的.
更何况:
我们只需要在第一次执行这个方法的时候同步,也就是说当ins实例化后,我们不再需要同步了
而如果我们加了synchronized,那么实例化后的每次调用getIns都是一种多余的消耗操作,是累赘

Ps:当然,如果哪些额外的负担你能接受(比如用的很少),那么添加`synchronized`的方法也是可以接受的,毕竟这是最简单的方式.

那么问题来了,如何改善?
如何确保单例,而又不影响性能?

性能进阶

接下去介绍一种更优秀的,线程安全的单例写法---双重检查锁模式(double check locking pattern)

public class DoubleCheck {
private volatile static DoubleCheck ins;
private DoubleCheck() {}
public static DoubleCheck getIns() {
if (null==ins){ //检查
synchronized (DoubleCheck.class){
if (null == ins) { //又检查一次
ins = new DoubleCheck();
}
}
}
return ins;
}}

注意这里的ins用了volatile关键字来修饰,为什么呢?
因为执行ins = new DoubleCheck()做了很多事情:

  1. 给ins分配内存
  2. 调用构造函数来初始化成员变量(可能会很久)
  3. 将ins对象指向分配的内存空间(执行完这步 ins才不为null)

上面的操作并不是原子操作,而jvm也可能重排序指令,导致第二三两步的执行顺序可能会被打乱,当第3步先于第2步完成,那么会导致有线程拿到了初始化未完毕的ins,那么就会错误,而这里利用了volatile禁止指令重排序优化特性,用来解决这个问题.

注:volatile 在java 5 后才有效,原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,当然现在不需要担心这个啦!

小结

双重检查非常适用于高并发,我们熟知的开源库Eventbus,ImageLoader等都是用的双重检查锁方式实现单例

不过它,写起来稍微复杂了些,有没有简单点的呢?
答案是:有!

饿汉式

直接上代码吧

public class Early {
private static final Early ins = new Early();
public static Early getIns() {
return ins;
}}

饿汉式的原理其实是基于classloder机制来避免了多线程的同步问题

饿汉式与之前提到的懒汉式不同,它在我们调用getIns之前就实例化了(在类加载的时候就实例化了),所以不是一个懒加载,这样就有几个缺点:

  1. 延长了类加载时间
  2. 如果没用到这个类,就浪费了资源(加载了但是没用它)
  3. 不能传递参数(很显然适用的场景会减少)

静态内部类

静态内部类原理同上,另外虽然它看上去有点恶汉式,但是与之前的恶汉有点不同,它在类Singleton加载完毕后并没有实例化,而是当调用getIns去加载Holder的时候才会实例化,静态内部类的方式把实例化延迟到了内部类的加载中去了!所以它比饿汉式更优秀!(偷偷告诉你Effective Java中也推荐这个方式)

例子:

public class Singleton {
private static class Holder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getIns(){
return Holder.INSTANCE;
}}

枚举

最后介绍一种,也是我在 EffectiveJava中看到的,但是在开发中我重来没看到过!

public enum Singleton{
INSTANCE;}

优点:简单,线程安全,防反序列化.
�本人不太了解,也没用过,所以不敢乱说了~~~
值得一提的是 EffectiveJava 也提倡这个方式

这样真的就能保证单例是个单例了吗?

我们采用了很多方式来保证实例的唯一性,但是真的够了吗?
如果采用反射呢?
如果使用多个ClassLoder呢?
那是不是会实例化多个了呢?
思考一下吧~

总结

单例的实现方式有好多种,实际开发中看需求而定,个人比较推荐双重检查锁和静态内部类,这两个我比较常用,其他的说实话我一般不用.
同时也可以看到单例也有很多需要学习以及思考的地方,完全弄懂也不容易~

给自己定了一个每周一个设计模式的学习计划,希望能够坚持下去吧.
以上代码可以在,好了,就这样,下次见.

More

聊完了单例的实现方式,其实单例还有一些比较有趣的,值得思考讨论的地方,下面推荐几个,有兴趣的可以去看看~

安利:


我的微信公众号:

531570-7d7d52ac63971762.jpg
微信公众号

转载地址:https://blog.csdn.net/u014400934/article/details/101489961 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:IntentService教给我什么
下一篇:RxJava之Subscription

发表评论

最新留言

第一次来,支持一个
[***.36.148.227]2022年06月21日 23时18分28秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

最新文章

215. 数组中的第K个最大元素。在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 2019-08-03 02:38:28
【微服务-Spring源码】Idea + Gradle + Spring源码环境搭建 2019-08-03 02:38:28
不使用任何内建的哈希表库设计一个哈希集合 具体地说,你的设计应该包含以下的功能。add(value):向哈希集合中插入一个值。 contains(value) :返回哈希集合中是否存在这个值。 2019-08-03 02:38:27
给你一个数组 nums ,数组中有 2n 个元素,按 [x1,x2,...,xn,y1,y2,...,yn] 的格式排列。 请你将数组按 [x1,y1,x2,y2,...,xn,yn] 格式重新排 2019-08-03 02:38:27
给你两个整数,n 和 start 。数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)且 n == nums.length 。 2019-08-03 02:38:26
MyISAM 和 InnoDB 锁总结 2019-08-03 02:38:26
给你一个长度为 n 的整数数组 nums,其中 n大于1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 2019-08-03 02:38:25
【每日一练】给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。 2019-08-03 02:38:25
【每日一练】你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 2019-08-03 02:38:24
给定两个列表 Aand B,并且 B 是 A 的变位(即 B 是由 A 中的元素随机排列后组成的新列表)。 2019-08-03 02:38:24
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 2019-08-03 02:38:23
【Java面试题】调用yield()、sleep()、notify()、wait()等方法对锁有何影响? 2019-08-03 02:38:23
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 2019-08-03 02:38:22
synchronized 不是万能,错误加锁 2019-08-03 02:38:22
给定一个二叉树,判断它是否是高度平衡的二叉树。 2019-08-03 02:38:21
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。 2019-08-03 02:38:21
Thread 和 Runnable 区别? 2019-08-03 02:38:20
Mybatis 2019-08-03 02:38:20
在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。 2019-08-03 02:38:19
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 2019-08-03 02:38:19