单例模式理解起来应该不难,可是若是是在多线程下应该如何安全地实现单例模式呢?,看到一篇挺好的文章,顺手转过来,待往后细细回味。html
原文出处:深刻浅出单实例SINGLETON设计模式shell
这里,我将直接给出一个Singleton的简单实现,由于我相信你已经有这方面的一些基础了。咱们姑且把这个版本叫作1.0版设计模式
1 // version 1.0
2 public class Singleton { 3 private static Singleton singleton = null; 4 private Singleton() { } 5 public static Singleton getInstance() { 6 if (singleton== null) { 7 singleton= new Singleton(); 8 } 9 return singleton; 10 } 11 }
在上面的实例中,我想说明下面几个Singleton的特色:(下面这些东西多是尽人皆知的,没有什么新鲜的)安全
固然,若是你以为知道了上面这些事情后就学成了,那得给你当头棒喝一下了,事情远远没有那么简单。多线程
1 // version 1.1
2 public class Singleton 3 { 4 private static Singleton singleton = null; 5 private Singleton() { } 6 public static Singleton getInstance() { 7 if (singleton== null) { 8 synchronized (Singleton.class) { 9 singleton= new Singleton(); 10 } 11 } 12 return singleton; 13 } 14 }
嗯,使用了Java的synchronized方法,看起来不错哦。应该没有问题了吧?!错!这仍是有问题!为何呢?前面已经说过,若是有多个线程同时经过(singleton== null)的条件检查(由于他们并行运行),虽然咱们的synchronized方法会帮助咱们同步全部的线程,让咱们并行线程变成串行的一个一个去new,那不仍是同样的吗?一样会出现不少实例。嗯,确实如此!看来,还得把那个判断(singleton== null)条件也同步起来。因而,咱们的Singleton再次升级成1.2版本,以下所示:函数
1 // version 1.2
2 public class Singleton 3 { 4 private static Singleton singleton = null; 5 private Singleton() { } 6 public static Singleton getInstance() { 7 synchronized (Singleton.class) { 8 if (singleton== null) { 9 singleton= new Singleton(); 10 } 11 } 12 return singleton; 13 } 14 }
不错不错,看似很不错了。在多线程下应该没有什么问题了,不是吗?的确是这样的,1.2版的Singleton在多线程下的确没有问题了,由于咱们同步了全部的线程。只不过嘛……,什么?!还不行?!是的,仍是有点小问题,咱们原本只是想让new这个操做并行就能够了,如今,只要是进入getInstance()的线程都得同步啊,注意,建立对象的动做只有一次,后面的动做全是读取那个成员变量,这些读取的动做不须要线程同步啊。这样的做法感受很是极端啊,为了一个初始化的建立动做,竟然让咱们达上了全部的读操做,严重影响后续的性能啊!性能
还得改!嗯,看来,在线程同步前还得加一个(singleton== null)的条件判断,若是对象已经建立了,那么就不须要线程的同步了。OK,下面是1.3版的Singleton。优化
// version 1.3
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton== null) { synchronized (Singleton.class) { if (singleton== null) { singleton= new Singleton(); } } } return singleton; } }
感受代码开始变得有点罗嗦和复杂了,不过,这多是最不错的一个版本了,这个版本又叫“双重检查”Double-Check。下面是说明:spa
至关不错啊,干得很是漂亮!请你们为咱们的1.3版起立鼓掌!线程
可是,若是你认为这个版本大攻告成,你就错了。
主要在于singleton = new Singleton()
这句,这并不是是一个原子操做,事实上在 JVM 中这句话大概作了下面 3 件事情。
可是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序多是 1-2-3 也多是 1-3-2。若是是后者,则在 3 执行完毕、2 未执行以前,被线程二抢占了,这时 instance 已是非 null 了(但却没有初始化),因此线程二会直接返回 instance,而后使用,而后瓜熟蒂落地报错。
对此,咱们只须要把singleton声明成 volatile 就能够了。下面是1.4版:
// version 1.4
public class Singleton { private volatile static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton== null) { synchronized (Singleton.class) { if (singleton== null) { singleton= new Singleton(); } } } return singleton; } }
使用 volatile 有两个功用:
1)这个变量不会在多个线程中存在复本,直接从内存读取。
2)这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操做后面会有一个内存屏障(生成的汇编代码上),读操做不会被重排序到内存屏障以前。
可是,这个事情仅在Java 1.5版后有用,1.5版以前用这个变量也有问题,由于老版本的Java的内存模型是有缺陷的。