這篇文章中我會用8種寫法來對單例模式進行優化安全
可是說實話在日常咱們進行代碼編寫的時候用不着那麼完美多線程
餓漢式:優化
優勢:簡單實用spa
缺點:不論該對象是否會被用到,都提早將對象實例化線程
1.首先咱們建立出一個靜態的不可更改的變量Instancecode
2.咱們將該類的構造方法的權限設置爲private,防止其餘類new對象對象
3.設置該對象的get方法blog
1 /** 2 * 餓漢式 3 * 類加載到內存後,就實例化一個單例,JVM保證線程安全 4 * (JVM保證每個class只會露到內存一次,那麼static變量在class露到內存以後立刻進行初始化,因此static變量也保證初始化這一次) 5 * 簡單實用,推薦使用 6 * 惟一缺點:無論用到與否,類加載時就完成實例化 7 */ 8 public class Mgr01 { 9 private static final Mgr01 Instance = new Mgr01(); 10 11 private Mgr01(){} 12 13 public static Mgr01 getInstance(){return Instance;} 14 15 public static void main(String[] args) { 16 /** 17 * 調用靜態方法經常使用的兩種方式 18 * 1.new對象調用靜態方法 19 * 2.類名打點調用靜態方法 20 * 這裏構造方法設爲私有訪問控制符,不能new對象 21 */ 22 Mgr01 m1 = Mgr01.getInstance(); 23 Mgr01 m2 = Mgr01.getInstance(); 24 System.out.println(m1 == m2); 25 } 26 }
第二種寫法與第一種寫法幾乎沒有區別,只是使用了靜態代碼塊進行對象的初始化
可是該寫法仍然沒有解決類加載時初始化的問題
1 /** 2 * 與Mgr01意思相同 3 */ 4 public class Mgr02 { 5 //這裏加上final沒有初始化,可是在下面必須加static靜態代碼塊進行初始化 6 private static final Mgr02 Instance; 7 8 static{ 9 Instance = new Mgr02(); 10 } 11 12 private Mgr02(){} 13 14 public static Mgr02 getInstance(){return Instance;} 15 16 public static void main(String[] args) { 17 Mgr02 m1 = Mgr02.getInstance(); 18 Mgr02 m2 = Mgr02.getInstance(); 19 System.out.println(m1 == m2); 20 } 21 }
懶漢式:內存
優勢:在須要的時候進行對象初始化,解決了以上兩種寫法的缺點get
缺點:帶來了線程不安全的問題
在進行判斷是否存在Instance實例時,咱們假設有現後兩個線程,一號線程剛判斷完發現沒有實例,正準備進行new時,二號線程忽然進入,判斷結果一樣爲沒有Instance實例,這時就會有兩個線程前後執行new操做
1 /** 2 * 懶漢式 3 * 達到了按需初始化的目的,可是帶來了線程不安全的問題 4 */ 5 public class Mgr03 { 6 //這裏不能加final,由於加上final就必須進行初始化new對象 7 private static Mgr03 Instance; 8 9 private Mgr03(){} 10 11 public static Mgr03 getInstance(){ 12 if (Instance == null){ 13 //多線程同時打入的時候容易在這裏產生偏差 14 Instance = new Mgr03(); 15 } 16 return Instance; 17 } 18 19 public static void main(String[] args) { 20 Mgr03 m1 = Mgr03.getInstance(); 21 Mgr03 m2 = Mgr03.getInstance(); 22 System.out.println(m1 == m2); 23 } 24 }
在getInstance方法上加鎖來保證線程安全,可是若是每一個線程在執行getInstance時都進行加鎖操做,那麼就會下降程序執行效率
1 /** 2 * 增長了線程的安全性,可是下降了程序執行效率 3 */ 4 public class Mgr04 { 5 private static Mgr04 Instance; 6 7 private Mgr04(){} 8 9 public static synchronized Mgr04 getInstance(){ 10 if (Instance == null){ 11 Instance = new Mgr04(); 12 } 13 return Instance; 14 } 15 public static void main(String[] args) { 16 Mgr04 m1 = Mgr04.getInstance(); 17 Mgr04 m2 = Mgr04.getInstance(); 18 System.out.println(m1 == m2); 19 } 20 }
試圖經過同步代碼塊的方式在保證線程安全的前提下提升效率
同步代碼塊只加在須要new實例的時候
這樣雖然提升了程序執行效率,可是顯然是不能保證線程安全的
假設有兩個線程:線程一在if條件判斷結束後發現沒有實例,正在往下執行可是尚未進入同步代碼塊時,線程二進入也一樣判斷沒有實例,線程二比線程一提早拿到鎖new出來實例後釋放鎖,等到線程二釋放鎖以後線程一又執行方法new出來實例。這樣就致使了線程的不安全性
1 public class Mgr05 { 2 private static Mgr05 Instance; 3 4 private Mgr05(){} 5 6 public static Mgr05 getInstance(){ 7 if (Instance == null){ 8 /** 9 * 試圖經過同步代碼塊的方式提升效率 10 * 可是這裏又很容易形成線程不安全問題 11 */ 12 synchronized (Mgr05.class){ 13 Instance = new Mgr05(); 14 } 15 } 16 return Instance; 17 } 18 public static void main(String[] args) { 19 Mgr05 m1 = Mgr05.getInstance(); 20 Mgr05 m2 = Mgr05.getInstance(); 21 System.out.println(m1 == m2); 22 } 23 }
雙重if判斷來保證只有一個實例對象
這樣即不會出現線程不安全問題,又保證了不會隨着類加載而建立出來實例
1 public class Mgr06 { 2 //加volatile主要是爲了防止指令重排 3 private static volatile Mgr06 Instance; 4 5 private Mgr06(){} 6 7 public static Mgr06 getInstance(){ 8 //第一個進入方法的線程進行Instance實例是否存在的判斷 9 if (Instance == null){ 10 //同步代碼塊進行加鎖 11 synchronized (Mgr06.class){ 12 //雙重鎖機制進行判斷Instance對象是否被實例 13 if (Instance == null){ 14 Instance = new Mgr06(); 15 } 16 } 17 } 18 return Instance; 19 } 20 public static void main(String[] args) { 21 Mgr06 m1 = Mgr06.getInstance(); 22 Mgr06 m2 = Mgr06.getInstance(); 23 System.out.println(m1 == m2); 24 } 25 }
使用靜態內部類來保證只有一個實例
靜態內部類在加載類的時候是不會被加載的,而getInstance方法在執行返回Instance實例調用靜態內部類時,靜態內部類纔會被加載,保證了在使用時纔會建立實例
JVM只會加載一次類,一樣也只會加載一次靜態內部類,這樣就保證了線程安全,只會產生一個實例對象
1 /** 2 * 使用靜態內部類保證單例,同時也保證線程安全 3 * 線程安全是用過JVM機制來保證的 4 * JVM只會加載一次Mgr07這個類,只會加載一次Mgr07Holder這個內部類 5 * 這也就保證了只會生成一個Instance實例 6 */ 7 public class Mgr07 { 8 private Mgr07(){} 9 10 /** 11 * 使用靜態內部類進行實現 12 * 當Mgr07這個類被加載時,裏面的內部類是不會被加載的,保證了類加載時不完成實例化 13 * 當調用getInstance方法時內部類纔會加載,也保證了只有一個實例 14 */ 15 private static class Mgr07Holder{ 16 private static final Mgr07 Instance = new Mgr07(); 17 } 18 19 public static Mgr07 getInstance(){ 20 return Mgr07Holder.Instance; 21 } 22 23 public static void main(String[] args) { 24 Mgr07 m1 = Mgr07.getInstance(); 25 Mgr07 m2 = Mgr07.getInstance(); 26 System.out.println(m1 == m2); 27 } 28 }
枚舉單例模式,枚舉類中沒有構造方法,保證了僅僅實例化一個對象
只有在調用Instance實例的時候纔會建立實例,保證了在使用時纔會建立實例
1 /** 2 * 枚舉單例模式:經過枚舉進行單例模式的實例建立 3 * 緣由:枚舉類中沒有構造方法 4 * 不只能夠解決線程同步,還能夠防止反序列化 5 */ 6 public enum Mgr08 { 7 8 Instance; 9 10 public static void main(String[] args) { 11 Mgr08 m1 = Mgr08.Instance; 12 Mgr08 m2 = Mgr08.Instance; 13 System.out.println(m1 == m2); 14 } 15 }