單例模式理解起來應該不難,可是若是是在多線程下應該如何安全地實現單例模式呢?,看到一篇挺好的文章,順手轉過來,待往後細細回味。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的內存模型是有缺陷的。