专栏首页艾小仙一个单例还能写出花来吗?

一个单例还能写出花来吗?

单例可以说是最简单的一个设计模式了,单例模式要求只能创建一个对象实例。通常的写法是声明私有的构造函数,提供静态方法获取单例的对象实例。

常见的单例写法就是饿汉式、懒汉式、双重加锁验证、静态内部类和枚举的方式,写法可能大家都知道,不过针对不同的写法还是有可以继续深挖一下的地方,让我们从最简单的几种写法开始回顾单例,不想看前面的话直接往后翻好了。

回顾几种实现方式

饿汉式

饿汉式的写法通常静态成员变量已经是初始化好的,优点是可以不加锁就获取到对象实例,线程安全,主要的缺点在于不是延加载,稍微存在内存的浪费,因为如果初始化的逻辑较为复杂,比如存在网络请求或者一些复杂的逻辑在内,就会产生内存的浪费。

懒汉式

懒汉式的写法解决了饿汉式浪费内存的问题,在真正需要获取实例对象的才去执行初始化。

通常一般来说可能会有两种方式,第一种就是不加锁的写法,很显然这样是肯定不行的,正常的方式一般都是通过同步锁的方式加锁获取实例对象。

但是这种实现方式在之前的JDK版本synchronized没有锁优化的情况每次获取单例对象性能存在很大的问题,于是乎有了DCL的写法。

双重加锁验证DCL

于是为了解决懒汉式性能的问题,双重加锁验证的写法诞生了,先判断一次空,真的为空再执行加锁,然后再判断一次。

这样的话,只有在实例对象是空的情况才会去加锁创建对象,性能问题得到了一定程度上的解决,也不会和饿汉一样有内存浪费的问题。

但是,这个写法也存在问题,就是会拿到未初始化完全的对象,我之前的一篇文章中也提到这个方式的问题,具体请看一次群聊引发的血案

让我这里复用一下我写过的东西。

从CPU的角度来看,instance = new Instance()可以分为分为几个步骤:

  1. 分配对象内存空间
  2. 执行构造方法,对象初始化
  3. instance指向分配的内存地址

实际上,由于指令重排的问题,2、3的步骤可能会发生重排序,那么问题就发生了。

instance先被指向内存地址,然后再执行初始化,如果此时另外一个线程来访问getInstance方法,就会拿到instance不是null,最后拿到的将是一个没有被完全初始化的对象!

现在也有很多人说这个问题在高版本的JDK中已经解决了,但是我是没发现有什么直接证据,如果你知道,请你告诉我。

静态内部类

这个通过JVM来保证创建单例对象的线程安全和唯一性,是比较好的办法。

Singleton类加载的时候,SingletonHolder不会加载,只有在调用getInstance方法的时候才会执行初始化,这样既起到了懒加载的作用,同时又使用到了JVM类加载机制,保证了单例对象初始化的线程安全。

这种方式也是目前比较推荐的一种方式。

枚举

通过枚举来实现单例是Effective Java作者 Josh Bloch 提倡的方式,也是单例模式的最佳实现方式。

为了看清楚枚举怎么实现单例模式的,我们来编译一下枚举生成的最终字节码。

执行javac Singleton.java生成class文件,接着执行javap -p Singleton.class,得到如下内容:

为了看到更详细的内容,我们执行 javap -c Singleton

通过最终生成的字节码,我们其实发现本质上枚举的初始化通过static代码块来进行初始化。

考虑下类加载的几个步骤,加载->验证->准备->解析->初始化,最终初始化就是执行static代码块,而static代码块是绝对线程安全的,只能由JVM来调度,这样保证了线程安全。

枚举的实现方式好处还不止于此,除了一目了然的实现简单之外,还能防止其他几种实现方式避免不了的几个问题。

再说几种方式的问题

反射破坏单例

除了枚举之外,其他的几种方式都可以通过反射的方式达到破坏单例的目的,就随便以一个实现方式来举例,这里最终的输出结果是false

如果拿去尝试反射创建枚举对象的话,则是会报错,可以自己动手尝试一下。

为什么会报错,可以直接看一下newInstance的源码,有一段特殊的关于枚举类型的判断,下图中我红色标记的部分。

序列化

除了众所周知的使用反射来破坏单例之外,还有另外一种能破坏单例的方式就是序列化。

对上面的饿汉方法实现序列化,然后得到的结果是false,序列化前后对象发生了改变。

其实关键的部分在于ois.readObject方法,一路跟踪最后找到一段代码如下:

所以很明显我们发现了最终实际上这里通过反射创建了一个新的对象,isInstantiable实际代表的应该是类或者属性是序列化的,那么久就返回true,我们这里肯定是true,所以最终产生了一个新的对象。

枚举为啥可以防止这个问题?枚举的实现方式不太一样而已,同样跟踪到枚举部分的实现逻辑。

下图中红框标注的部分就是枚举类型去实现反序列化的逻辑,最终只是通过valueOf方法查找枚举,不存在新建一个对象的逻辑。

那么,怎么防止其他方式序列化对单例的破坏?再往下看看源码,红框标注的意思只要有readResolve方法就可以解决问题了。

实际上,最终解决方案也很简单,单例类加上方法即可。

好了,打完收工。现在是北京时间4月15日凌晨1点整,困了,睡觉。

·················END·················

本文分享自微信公众号 - 艾小仙(aixiaoxianren),作者:艾小仙

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何写出一个性能优化的单例模式

    单例模型是面试当中最常见的一种设计模式,它是一种对象创建模式,用于产生一个对象的具体实例,可以确保系统中一个类只产生一个实例。

    朱季谦
  • 你真的能写好一个单例么?

    单例可能是 iOS 开发者最熟悉设计模式之一了。 我们的项目里头也使用了很多单例?。 最近为了解决项目中单例的 bug 而花费了两天多的时间,发现用 ObjC ...

    iOSSir
  • 你真的能写好一个单例么?

    原文地址:https://juejin.im/post/5cb067676fb9a0688360f987”

    iOSSir
  • 还在一个个写规则?来了解下爬虫的智能化解析吧!

    爬虫是做什么的?是帮助我们来快速获取有效信息的。然而做过爬虫的人都知道,解析是个麻烦事。

    崔庆才
  • 数据结构能干吗,我花了一夜给女朋友写个走迷宫游戏

    先看效果图(在线电脑尝试地址http://biggsai.com/maze.html):

    bigsai
  • c#一个简单的实例告诉你,多继承还可以这么来

    我想多继承,要怎么搞???我想你一定会说“接口”,那么你有没有遇到这样的问题,你需要在一个类中继承另外2个类的所有方法,你要怎么做呢???难道要Coyp实现代码...

    冰封一夏
  • 你有过连喝5杯咖啡,一个代码都写不出来的时候吗?

    即使是最优秀的程序员也会遭遇无法解决的软件工程问题。碰到这样的问题,并不一定意味着你缺乏技能或知识。 编程不是一项容易的工作,我们可以通过采取非正统的方法来保持...

    BestSDK
  • 单身税的时代就要来临,你还没有用Python帮你找一个女朋友吗?

    单身税的历史可以追溯到2015年韩国的新政, 低生育率逼得韩国产生了这一政策。  现在我国也要实行这一政策, 很多单身狗就接受不了了

    猫咪编程
  • PySimpleGUI 进阶| 原来用Python做一个图片查看系统,还能这么简单!

    大家好,在昨天的文章中我们已经介绍了为什么以及如何基本使用PySimpleGUI,并且对一些比较常用的元素(element)也有所了解。

    刘早起
  • 原创 | 这道题codeforces的简单题差点做了我一下午,你能解出来吗?

    大家好,今天周末算法专题选的问题是codeforces contest1400中的B题,RPG Protagonist,翻译过来就是RPG游戏当中主人公。看起来...

    TechFlow-承志
  • 一个简单登录页面就能看出你的设计功底,不信?|来试试

    一个合格的注册登录页面,应该是具有清晰的操作流程,良好的交互细节和美观的视觉设计。 清晰的操作流程 APP的注册登录有四种情况: 不需要注册登录 常见于系统自带...

    BestSDK
  • 码德需求?这不就是产品给我留的数学作业!

    「最终」,所有的这些不合理交织在一起,就是你能看到的一坨坨的代码!「所以」,要想把代码写好、写美,写到自己愿意反复欣赏,那么基本需要你有一定的:基础能力(数据结...

    小傅哥
  • python能开发游戏吗

    用锤子能造汽车吗? 谁也没法说不能吧?历史上也确实曾经有些汽车,是用锤子造出来的。但一般来说,还是用工业机器人更合适对吗?

    砸漏
  • 乐固-签名APK-会出来一个aligned的apk包,在签名的时候你们还做了什么操作吗?

    乐固-签名APK-会出来一个aligned的apk包,在签名的时候你们还做了什么操作吗?

    用户6184495
  • 多些时间能少写些代码

     在现在这个浮躁的时期,再加上敏捷咨询师们念的歪经,他们让人感觉上就像是软件产品是可以在很短的时间内高质量的完成的,这令那些管理者们很兴奋,就像巴甫洛夫的条件反...

    用户1289394
  • 谈面试时从写一个单例开始究竟能问多深及终极解决方案

    静儿
  • 只加两行代码,为什么要用两天?

    不管你是互联网公司的正规军,还是兼职外包的开发者,你或多或少都会遇到各种各样来自产品、客户、老板们的花样繁多的需求,而且他们都一致认为:这个需求很简单。

    深度学习与Python
  • 一枚程序员眼中的单元测试

    如今程序员群体赶上了中国最庞大的农民群体,大街上随便抓一把,十有八九是程序员,还一个刚从某国企离职报名参加软件培训班。我想码农的称号或许就是这么来的吧。

    袁慎建@ThoughtWorks
  • 学单片机前要做好这些思想准备,不然你就白学了

    写出这部分,着实的好笑,为什么呢?大家就说这还要说,我要学单片机,肯定是做好思想准备了啊,但是实际呢,好多同学经常会问我说单片机难学吗?这么贵的开发板,有没有便...

    单片机技术宅

扫码关注云+社区

领取腾讯云代金券