JavaSEjava进阶干货Android知识

由单例模式的双判空所展开的思考

2017-11-30  本文已影响249人  markRao

相信很多朋友对于单例模式都很熟悉,一般常见的就七八种,百度一大堆,这里聊一下双判空情况下的单例模式。
双判空单例是由单判空所演变而来的,是原来的一些程序员为了提升效率,主要是在JDK版本比较低的时候,锁是比较低效的,双判空从逻辑上可以解决线程的吊起、等待、调度等开销。但是双向判空的单例由于java虚拟机内存分配模型的问题,它并不能实现多线程安全了。


双判空单例模式.png

从设计的逻辑上来说,在锁的外层加上判空可以有效的减少判断锁的开销,但是java实例化从逻辑层面有三个步骤,
1,分配内存空间。
2,对象实例化(即为函数和属性分配内存)。
3,把内存空间的地址指向对象引用。
第二步的操作必须依赖于第一步,因为没有分配内存空间就无法为函数、属性分配内存,但是第三步并不需要依赖于第二步的,因为它只需要内存地址。所以虚拟机在执行过程中会对其实例化的过程进行重新排序,也成为java指令重排序,那么反向思考,虚拟机的设计者为什么要进行指令重排序,总所周知,随着CPU的不断更新迭代,其性能也是大大提升,为了避免在执行内存时造成的CPU空置资源浪费(即慢速内存和高速CPU的不匹配),按照正常的执行顺序,第三步要等待第二步为函数和属性内存分配后才走。
所以虚拟机为了“优化”,进行了指令重排序,即把第三步先于第二步去执行,让逻辑上后面的指令在时间上早与前面的指令,那这样其实就造成了双判空的单例模式实际上得到了一个“半实例化对象”,因为我们的判断条件是if(null == 对象引用),null就是没有指向内存,只要分配了内存空间那该判断条件就不成立。
当然,部分人也很快意识到这个缺陷,给当前的变量加上volatile关键字,就可以解决这个问题,因为这个关键字内部实现可以避免虚拟机的指令重排序,volatile仅仅只是内存的可见性,它和高并发、原子性没有啥关系,只是避免了jvm的指令重排才实现了多线程安全,它的设计初衷就是为了解决锁的低效性,但是现在的锁已经优化很多,几乎可以忽略不计。如果要实现多线程安全,推荐使用静态内部类,这种方式得益于类的加载机制,只会存在一个,另一个则是使用枚举,java的语法糖,简单高效。
记起早先去一家公司面试的时候,还说起这个单例模式,现在想起真是太小太年轻。。。。。。。

上一篇下一篇

猜你喜欢

热点阅读