DCL之单例模式 (3)

通过在第10行又加了一层if判断,也就是所谓的Double Check Lock。也就是说即便拿到锁了,也得去作一步判断,如果这时判断对像不为空,那么就不用再创建对象,直接返回就可以了,很好的解决了“改进2”中的问题。但这里第8行是不是可以去了,我个人觉得都行,保留第8行的话,是为了提升效率,因为如果去了,每个线程过来就直接抢锁,抢锁本身就会影响效率,而if判断就几ns,且大部分线程是不需要抢锁的,所以最好保留。
到这DCL 单例的原理就介绍完了,但是还是有一个问题。就是需要考虑指令重排序的问题,因此得加入volatile来禁止指令重排序。继续分析代码,为了分析方便这里将Singleton代码简化:

public class Singleton { int a = 5;//考虑指令重排序的问题 }

singleton = new Singleton()的字节码如下:

0: new #2 // class com/reasearch/Singleton 3: dup 4: invokespecial #3 // Method com/reasearch/Singleton."<init>":()V 7: astore_1

先不管dup指令。这里补充一个知识点,创建对象的时候,先分配空间,类里面的变量先有一个默认值,等调用了构造方法后才给变量赋值。例如int a = 5刚开始的时候 a = 0。字节码指令执行过程如下,

new 分配空间,a=0

invokespecial 构造方法 a=5

astore_1将对象赋给singleton

这是理想的状态,2和3语义和逻辑上没有什么关联,因此jvm可以允许这些指令乱序执行,即先执行3再执行2 。回到改进3,假如线程1再执行第16行代码时,指令的执行顺序是1,3,2,当执行完3时,时间片用完了,此时a=0,也就是说初始化到一半时就挂起了。这时线程2 来了,第8行判断,singleton肯定不为空,因此直接返回一个Singleton的对象,但其实这个对象是一个问题对象,是一个半初始化的对象,即a=0。这就是指令重排序造成的,因此为了防止这种现象的发生加上关键字volatile就可以了。因而,最终DCL之单例模式的代码完整版如下:

完整版 public class Singleton { private volatile static Singleton singleton = null;//加上volatile private Singleton(){} public static Singleton getInstance(){ /* 一堆业务处理代码 */ if(null == singleton){ synchronized(Singleton.class){//锁粒度变小 if(null == singleton){//DCL try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton(); } } } return singleton; } }

至此,可以告一段落了,相信很多小伙伴都会写单例,但是了解其中的原理还是有一定的难度,大家一起加油!

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwyxyx.html