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

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

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

回顾几种实现方式

饿汉式

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

image

懒汉式

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

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

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

image

双重加锁验证DCL

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

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

image

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

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

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

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

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

image

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

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

静态内部类

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

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

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

image

枚举

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

image

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

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

image

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

image

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

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

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

再说几种方式的问题

反射破坏单例

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

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

image

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

image

序列化

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

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

image

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

image

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

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

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

image

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

image

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

image

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,709评论 1 9
  • 版本:ios 1.2.1 亮点: 1.app角标可以实时更新天气温度或选择空气质量,建议处女座就不要选了,不然老想...
    我就是沉沉阅读 6,876评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 2,583评论 2 9