一个单例还能写出花来吗?
单例可以说是最简单的一个设计模式了,单例模式要求只能创建一个对象实例。通常的写法是声明私有的构造函数,提供静态方法获取单例的对象实例。
常见的单例写法就是饿汉式、懒汉式、双重加锁验证、静态内部类和枚举的方式,写法可能大家都知道,不过针对不同的写法还是有可以继续深挖一下的地方,让我们从最简单的几种写法开始回顾单例,不想看前面的话直接往后翻好了。 回顾几种实现方式饿汉式
饿汉式的写法通常静态成员变量已经是初始化好的,优点是可以不加锁就获取到对象实例,线程安全,主要的缺点在于不是延加载,稍微存在内存的浪费,因为如果初始化的逻辑较为复杂,比如存在网络请求或者一些复杂的逻辑在内,就会产生内存的浪费。
懒汉式
懒汉式的写法解决了饿汉式浪费内存的问题,在真正需要获取实例对象的才去执行初始化。
通常一般来说可能会有两种方式,第一种就是不加锁的写法,很显然这样是肯定不行的,正常的方式一般都是通过同步锁的方式加锁获取实例对象。
但是这种实现方式在之前的JDK版本 synchronized 没有锁优化的情况每次获取单例对象性能存在很大的问题,于是乎有了DCL的写法。
双重加锁验证DCL
于是为了解决懒汉式性能的问题,双重加锁验证的写法诞生了,先判断一次空,真的为空再执行加锁,然后再判断一次。
这样的话,只有在实例对象是空的情况才会去加锁创建对象,性能问题得到了一定程度上的解决,也不会和饿汉一样有内存浪费的问题。
但是,这个写法也存在问题,就是会拿到未初始化完全的对象,我之前的一篇文章中也提到这个方式的问题,具体请看一次群聊引发的血案。
让我这里复用一下我写过的东西。
从CPU的角度来看,instance = new Instance()可以分为分为几个步骤: 分配对象内存空间 执行构造方法,对象初始化 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点整,困了,睡觉。
后台回复【pdf】获取百本计算机电子书和大厂面试精华,文章每周持续更新。我是艾小仙,阿里巴巴技术专家,我们下期见!
面经PDF整理
化妆完后脸上起皮怎么办当我们想要出门逛街之前肯定就是要化一个美美的妆容,那么如果我们化完妆之后脸上要是起皮怎么办呢?有什么解决方法吗,接下来就让小编带你了解一下吧。化妆完后脸上起皮怎么办如果只是单纯的脸
奇山秀水游桂林之五鬼斧神工银子岩旅游界一直有一种说法,叫作五岳归来不看山,黄山归来不看岳,后续又有人添加了九寨归来不看水,阳朔归来不看洞二句,说明阳朔溶洞地位之高。另外再算上桂林山水甲天下,阳朔山水甲桂林的加持,
牡丹丛拍照穿啥衣服又是一年开花最盛的时候啦,如果带爸爸妈妈去看牡丹花,应该穿什么才会拍照好看呢?感兴趣的小伙伴就一起来看看穿搭建议吧。牡丹丛拍照穿啥衣服最好是穿一件显色的衣服,如果是白色的外套也是比
脸黑女生穿什么颜色的衣服显皮肤白脸黑穿什么颜色的衣服显得皮肤白皮肤黑的人要穿正色的衣服才能够显白,比如黑色以及红色,但是穿红色的时候需要跟其他衣服进行搭配才能够显白。在很多人的认知里,皮肤黑的人就不能穿黑色,因为
黄色衣服适合什么肤色的人穿搭相信很多人穿搭的时候都会根据自己的肤色去挑选比较适合的颜色进行搭配,黄色是很多时尚秀场都有的颜色,那么这个时尚的颜色比较适合什么肤色的人穿好呢?黄色适合什么肤色的人使用黄色系是许多
白色衣服上弄到眼线液洗不掉怎么办白衣服是我们经常穿的衣服,主要是比较好搭配而且整体更加的干净一些,眼线液是很多女生都会使用的,要是眼线液不小心滴到了白色的衣服上应该怎么办。白衣服上眼线液洗不掉怎么办洗洁精因为眼线
肩膀宽大女生穿衣服有哪些禁忌大家在穿搭的时候都是要根据自己的身材去挑选单品,对于肩部比较宽的女生来说不是所有的衣服都适合穿着,那么具体什么衣服不太适合肩部比较宽的女生穿呢?肩宽的女生穿衣禁忌肩宽的女生不要穿紧
衣服号,S,L,M分别是多少我们买衣服的时候都需要根据自己的身材去选择尺码,衣服的尺码有很多,每个尺码都有对应的身高和体重,那么你知道S,L,M分别是多高和多重呢?衣服号,S,L,M分别是多少Xs加小号(ex
舞韵瑜伽穿什么样的衣服很多人为了保持一个比较好的身材都会去到舞蹈房练瑜伽,不过练瑜伽也是有着专业的服装的,那么舞韵瑜伽穿什么样的衣服比较合适呢?舞韵瑜伽穿什么样的衣服宽松短袖or修身背心牛仔or运动短裤
红色衣服和什么颜色的搭配好,好看的颜色搭配规则红色是一种很喜庆的颜色,穿起来会让整个人都非常有精神的感觉,当然,红色的搭配也是非常有讲究的,下面小编就跟大家分享一些红色衣服不错的色彩搭配方法吧。红色外套里面配什么颜色1黑色黑色
很适合上班族美女做的美甲,来看看这几款有气质干练还不会花哨平时我们上班除了服饰打扮外,还会注重美甲的打扮。有气质而干练的美甲款式,能让一天都有精神,干气十足,下面就来分享几款适合上班的美甲吧。深蓝极光跳色美甲氛围感美甲绝绝子,高级感的调色