一.單例模式的概述:
單例模式,顧名思義就是一個類只有一個實例,並且類負責創建自己的對象,這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
為什么使用單例模式而不使用靜態方法?
從面向對象的角度講:
雖然都能實現目的,但是他們一個是基於對象,一個是面向對象的,就像我們不面相對象也能解決問題一樣,面相對象的代碼提供一個更好的編程思想。
如果一個方法和他所在類的實例對象無關,那么它就應該是靜態的,反之他就應該是非靜態的。如果我們確實應該使用非靜態的方法,但是在創建類時又確實只需要維護一份實例時,就需要用單例模式了。
比如說我們在系統運行時候,就需要加載一些配置和屬性,這些配置和屬性是一定存在了,又是公共的,同時需要在整個生命周期中都存在,所以只需要一份就行,這個時候如果需要我再需要的時候new一個,再給他分配值,顯然是浪費內存並且再賦值沒什么意義,所以這個時候我們就需要單例模式或靜態方法去維持一份且僅這一份拷貝,但此時這些配置和屬性又是通過面向對象的編碼方式得到的,我們就應該使用單例模式,或者不是面向對象的,但他本身的屬性應該是面對對象的,我們使用靜態方法雖然能同樣解決問題,但是最好的解決方案也應該是使用單例模式。
從功能上講:單例模式可以控制單例數量;可以進行有意義的派生;對實例的創建有更自由的控制;
二.單例模式的5種寫法:
-
餓漢式
-
簡單懶漢式(在方法加鎖)
-
DCL雙重檢測加鎖(進階懶漢式)
-
靜態內部類實現懶漢式(最推薦寫法)
-
枚舉方式(最安全、簡潔寫法)
寫單例模式的代碼其實可以簡單的分為三步:
-
將構造函數私有化
-
在類的內部創建實例
-
提供獲取唯一實例的方法
2.1餓漢式
1 public class model1 { 2 //1.將構造函數私有化,不可以通過new的方式來創建對象 3 private model1(){}; 4 //2.在類的內部創建實例 5 private static model1 model1 = new model1(); 6 //3.提供獲取唯一實例的方法 7 public static model1 getLl(){ 8 return model1; 9 } 10 }
這種方法一上來就創建對象,如果一直用不到則造成資源浪費
2.2簡單懶漢式
1 public class model2 { 2 //1.將構造函數私有化,不可以通過new的方式來創建對象 3 private model2(){}; 4 //2.先不創建對象,等用到的時候再創建 5 private static model2 model2 = null; 6 //3.提供獲取唯一實例的方法,當這個方法被調用,說明用到了 7 public static model2 getLl(){ 8 //如果model2還沒有別創建,則創建,如果已經有了,直接返回 9 if (model2 == null){ 10 model2=new model2(); 11 } 12 return model2; 13 } 14 }
這種方法是在用到的時候再創建對象,但需要注意的是,這種方法在多線程環境下需要加鎖,如下:
1 public class model2 { 2 //1.將構造函數私有化,不可以通過new的方式來創建對象 3 private model2(){}; 4 //2.先不創建對象,等用到的時候再創建 5 private static model2 model2 = null; 6 //3.提供獲取唯一實例的方法,當這個方法被調用,說明用到了 7 public static synchronized model2 getLl(){ 8 //如果model2還沒有別創建,則創建,如果已經有了,直接返回 9 if (model2 == null){ 10 model2=new model2(); 11 } 12 return model2; 13 } 14 }
2.3DCL雙重檢測加鎖
如果采用上面的方法,在方法上加鎖,在多線程環境下性能比較低,所以將鎖的范圍降低
1 public class model2 { 2 //1.將構造函數私有化,不可以通過new的方式來創建對象 3 private model2(){}; 4 //2.先不創建對象,等用到的時候再創建 5 private static model2 model2 = null; 6 //3.提供獲取唯一實例的方法,當這個方法被調用,說明用到了 7 public static model2 getLl(){ 8 //如果model2還沒有別創建,則創建,如果已經有了,直接返回 9 if (model2 == null){ 10 synchronized(model2.getClass()){ 11 model2=new model2(); 12 } 13 } 14 return model2; 15 } 16 }
將范圍降低后會出現一個問題:
線程A和線程B同時調用getLl()方法,他們同時判斷model2==null
,進入了if代碼塊了
此時線程A得到CPU的控制權-->進入同步代碼塊-->創建對象-->返回對象
線程A完成了以后,此時線程B得到了CPU的控制權。同樣是-->進入同步代碼塊-->創建對象-->返回對象
很明顯的是:該類返回的不是一個實例,所以上面的代碼是不行的!
解決方法是在進入同步代碼塊時在判斷一下對象是否存在
1 public class model2 { 2 //1.將構造函數私有化,不可以通過new的方式來創建對象 3 private model2(){}; 4 //2.先不創建對象,等用到的時候再創建 5 private static model2 model2 = null; 6 //3.提供獲取唯一實例的方法,當這個方法被調用,說明用到了 7 public static model2 getLl(){ 8 //如果model2還沒有別創建,則創建,如果已經有了,直接返回 9 if (model2 == null){ 10 synchronized(model2.getClass()){ 11 if (model2==null){ 12 model2=new model2(); 13 } 14 } 15 } 16 return model2; 17 } 18 }
最后還需要在對象上加volatile關鍵字,防止出現重排序問題
1 public class model2 { 2 //1.將構造函數私有化,不可以通過new的方式來創建對象 3 private model2(){}; 4 //2.先不創建對象,等用到的時候再創建 5 private static volatile model2 model2 = null; 6 //3.提供獲取唯一實例的方法,當這個方法被調用,說明用到了 7 public static model2 getLl(){ 8 //如果model2還沒有別創建,則創建,如果已經有了,直接返回 9 if (model2 == null){ 10 synchronized(model2.getClass()){ 11 if (model2==null){ 12 model2=new model2(); 13 } 14 } 15 } 16 return model2; 17 } 18 }
2.4靜態內部類實現懶漢式
1 public class model3 { 2 private model3(){}; 3 //使用內部類的方式來實現懶加載 4 private static class LazyHolder{ 5 //創建單例對象 6 private static final model3 INSTANCE=new model3(); 7 } 8 public static final model3 getInstance(){ 9 return LazyHolder.INSTANCE; 10 } 11 }
2.5枚舉方式
1 public enum model4 { 2 MODEL_4, 3 }
這種方法簡單,可以防止多次實例,也是比較推薦的一種.