單例模式的8種寫法

這篇文章中我會用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 }
相關文章
相關標籤/搜索