Java中的GOF23(23中設計模式) 單例模式(Singleton) 在Java這這門語言裡面,它的優點在於它本身的可移植性上面,而要做到可移植的話,本身就需要一個中介作為翻譯工作,以達到本地和Java的統一,但是就這點而言就相當的消耗資源,所以就Java程式員需要不斷的去優化自己的代碼。今天所 ...
Java中的GOF23(23中設計模式)--------- 單例模式(Singleton)
在Java這這門語言裡面,它的優點在於它本身的可移植性上面,而要做到可移植的話,本身就需要一個中介作為翻譯工作,以達到本地和Java的統一,但是就這點而言就相當的消耗資源,所以就Java程式員需要不斷的去優化自己的代碼。今天所研究的單例模式就是在這樣的條件下產生的,
所謂單例模式,就是只有一個實例,在堆裡面只有一個。假如我們的實例,就需要一個,但是會多次用到,這樣的話就會出現很尷尬的問題。
比如:
- Windows的TaskManager(任務管理器)就是很典型的只需要一個實例,
- Windows的Recycle(回收站)在系統中,回收站只維護一個實例
- 在我們的項目裡面,會常常使用到讀取配置文件的類,
- 網站的計數器,一般也採用單例模式,否則難以實現同步
- 資料庫連接池設計一般也採用單例模式,因為資料庫連接是一種資料庫資源
- 操作系統的文件系統,因為一個操作系統只能有一個文件系統
- Application 也是單例模式
- Spring中,每個Bean預設就是單例的,這樣做的優點是Spring容器可以管理
- servlet編程中,每個Servlet也是單例的
- 在Spring MVC/struts 1 中,所使用到的控制對象也是單例的
在上述裡面我們瞭解到,單例模式在我們項目中,幾乎是天天出現,所以在這裡,我們仔細研究一下,這種設計模式的怎麼實現最好(說到實現,它的實現我們大多數人只知道有兩種,而還有三種模式知道的人不是很多,以及利用反序列化,反射漏洞去強制解除單例)
- 餓漢模式
- 使用static屬性來保持對象的單例模式,但是必須在類載入的時候載入,所以沒有延遲實例化的性能
* 而在得到實例的對象中,沒有對資源的同步鎖,所以調用效率高
* 而使用JVM類載入器,JVM底層天然是線程安全的, - 優點:線程安全調用率較高
- 缺點:不能延遲載入
1 public class Eager_Singleton { 2 3 private static Eager_Singleton singleton = new Eager_Singleton(); 4 5 private Eager_Singleton(){} 6 7 public static Eager_Singleton newInstance(){ 8 return singleton; 9 } 10 }
- 使用static屬性來保持對象的單例模式,但是必須在類載入的時候載入,所以沒有延遲實例化的性能
- 懶漢模式
- 依舊使用static屬性來保持對象的單例模式,但是在方法裡面new出來,當我們去調方法的時候,再去載入,就是懶人,懶得一上來就載入,但是就因為在方法裡面實例化,所以我們要在該方法上面使用鎖的概念來對他進行同步處理,如果不加鎖,那我們在A線程裡面去掉用它和在B線程裡面去調用它他就不是一個概念了
- 優點:可以延時載入,並且線程是安全的
- 缺點:就是方法實現了同步,所以資源的利用率比較低。
- 依舊使用static屬性來保持對象的單例模式,但是在方法裡面new出來,當我們去調方法的時候,再去載入,就是懶人,懶得一上來就載入,但是就因為在方法裡面實例化,所以我們要在該方法上面使用鎖的概念來對他進行同步處理,如果不加鎖,那我們在A線程裡面去掉用它和在B線程裡面去調用它他就不是一個概念了
/** * 懶漢單例模式 * @author 劉酸酸 * */ public class Sluggard_Singleton { private static Sluggard_Singleton singleton = null; private Sluggard_Singleton(){} public synchronized static Sluggard_Singleton newInstance(){ if(singleton == null){ singleton = new Sluggard_Singleton(); } return singleton; } }
- 雙重檢測式
- 將同步塊內部下方的代碼放至到IF內部
- 所面臨的問題:由於編譯器優化等原因JVM底層內部模型的原因,偶爾會出問題,不建議使用。
/** * 雙重檢查鎖實現單例模式 * @author 劉酸酸 * */ public class DoubleCheck_Singleton { private static DoubleCheck_Singleton instance = null; public static DoubleCheck_Singleton getInstance() { if (instance == null) { DoubleCheck_Singleton sc; synchronized (DoubleCheck_Singleton .class) { sc = instance; if (sc == null) { synchronized (DoubleCheck_Singleton .class) { if(sc == null) { sc = new DoubleCheck_Singleton (); } } instance = sc; } } } return instance; } private DoubleCheck_Singleton () { } }
-
靜態內部類方式實現
- 該方式是結合懶漢式和餓漢式的優點,
/** * 測試靜態內部類實現單例模式 * 這種方式:線程安全,調用效率高,並且實現了延時載入! * @author 劉酸酸 * */ public class StaticInnerClass{ private static class StaticInnerClass{ private static final StaticInnerClass instance = new StaticInnerClass(); } private StaticInnerClass(){ } //方法沒有同步,調用效率高! public static StaticInnerClassgetInstance(){ return StaticInnerClass.instance; } }
- 枚舉實現
- 在Java的JVM裡面就是一種天然的單例模式,那就是枚舉類型,如果使用枚舉類型的話會受到JVM底層的保護
-
比如使用反序列化,和使用反射是不能打破這個原則的
-
-
優點:上訴
-
缺點:不能延遲載入
- 在Java的JVM裡面就是一種天然的單例模式,那就是枚舉類型,如果使用枚舉類型的話會受到JVM底層的保護
/** * 測試枚舉式實現單例模式(沒有延時載入) * @author 劉酸酸 * */ public enum Enum_Singleton{ //這個枚舉元素,本身就是單例對象! INSTANCE; //添加自己需要的操作! public void xxxxx(){ } }
- 如何防止反序列化,反射破環單例模式(枚舉除外)
- 在反射中如果我們調用了私有的構造器的話,我們就會拋異常,但是會有人跳過合法檢測,這樣是可以訪問到我們的私有構造器的
import java.lang.reflect.Constructor; /** * 測試反射和反序列化破解單例模式 * @author 劉酸酸 * */ public class Main{ public static void main(String[] args) throws Exception { //通過反射的方式直接調用私有構造器 Class<Singleton> clazz = (Class<Singleton>) Class.forName("com.suansuan.singleton.Singleton"); Constructor<Singleton> c = clazz.getDeclaredConstructor(null); //跳過合法檢查 c.setAccessible(true); Singleton s1 = c.newInstance(); Singleton s2 = c.newInstance(); System.out.println(s1); System.out.println(s2);
}
}
-
- 反序列化時,我們也不能得到一個對象,所以,下述代碼解決這兩種問題
/** * 測試懶漢式單例模式(如何防止反射和反序列化漏洞) * @author 劉酸酸 * */ public class Singleton implements Serializable { //類初始化時,不初始化這個對象(延時載入,真正用的時候再創建)。 private static Singleton instance; private Singleton (){ //私有化構造器 //反射時,我們作如下操作既可以規避,跳過合法檢測的非法訪問 if(instance!=null){ throw new RuntimeException(); } } //方法同步,調用效率低! public static synchronized Singleton getInstance(){ if(instance==null){ instance = new Singleton (); } return instance; } //反序列化時,如果定義了readResolve()則直接返回此方法指定的對象。而不需要單獨再創建新對象! private Object readResolve() throws ObjectStreamException { return instance; } }
- 反序列化時,我們也不能得到一個對象,所以,下述代碼解決這兩種問題
總結:使用枚舉去替代餓漢式,使用靜態內部類去替代懶漢式
第一次寫博客,謝謝大家如果有不對的房指出來,我們共同進步,共同發展,謝謝大家