01-單例設計模式 第一章:單例模式核心作用 (1)保證一個類只能有一個實例(一個對象) (2)並且提供一個供外界訪問該實例的全局訪問點 第二章:常見應用場景 (1)windows的任務管理器、回收站 (2)項目中,讀取配置文件的類,一般只有一個對象。沒必要每次使用配置文件的數據都要new一個對象去 ...
01-單例設計模式
第一章:單例模式核心作用
(1)保證一個類只能有一個實例(一個對象)
(2)並且提供一個供外界訪問該實例的全局訪問點
第二章:常見應用場景
(1)windows的任務管理器、回收站
(2)項目中,讀取配置文件的類,一般只有一個對象。沒必要每次使用配置文件的數據都要new一個對象去讀取
(3)網站的計數器,一般採用單例模式設計,否則無法做到同步
(4)資料庫的連接池設計,一般使用單例模式設計
(5)在spring中,每個bean預設就是單例模式,這樣做的優點是spring容器方便管理
(6)在servlet編程中,每個Servlet也是單例模式
第三章:單例模式的優點
(1)減少系統性能的開銷:當一個對象的產生需要比較多的資源時,如需要讀取配置、產生其他依賴對象時,則可以通過在應用啟動時直接產生一個單例對象,然後永久存留在記憶體中的方式來解決。
(2)可以在系統設置全局的訪問點。優化共用資源訪問,例如可以設計一個單例類,負責所有數據表的映射處理
第四章:常見的單例模式實現方式及優缺點
4.1:餓漢式(重點)
特點:線程安全、調用效率高;但是,不能延時載入
在系統啟動的時候,載入該類的時候,由於static的原因,會立即去載入該類的實例化,同時也由於是static,該類在記憶體中只有這一個,達到單例的要求。但是,由於是立即載入,會占用系統存儲空間,有一定的缺陷。
由於將無參構造器私有化,所有外界想要使用該類,必須通過提供的全局唯一訪問點,拿到該類的實例化對象。不管外界獲取了多少次對象,在記憶體中,該類的實例對象只有一個。
public class SingletonDemo02 { private static /*final*/ SingletonDemo02 s = new SingletonDemo02(); private SingletonDemo02(){} // 私有化構造器 public static /*synchronized*/ SingletonDemo02 getInstance(){ return s; } }
4.2:懶漢式(重點)
特點:線程安全、調用效率不高;但是,可以延時載入
在系統啟動的時候,載入該類時,並不會立即去初始化該類;
而是在調用該類的getInstance()方法時,判斷當前類是否被創建,如果沒有被創建,則進行創建對象,返回。如果已經創建了,直接返回對象。
同時,考慮到併發的情況下,需要使用synchronized,保證每次只有一個線程去訪問該類的方法。保證實例化對象的唯一性。
這種方式,屬於延遲載入,真正用到該類的時候才會去載入。提高了資源利用率,但是調用getInstance()方法都要同步,併發效率低下。
public class SingletonDemo01 { private static SingletonDemo01 s; private SingletonDemo01(){} // 私有化構造器 public static synchronized SingletonDemo01 getInstance(){ if(s==null){ s = new SingletonDemo01(); } return s; } }
4.3:雙重檢測鎖式(瞭解)
特點:由於JVM底層內部模型原因,偶爾會出問題,不建議使用
這個模式將同步內容下方到if內部,提高了執行的效率不必每次獲取對象時都進行同步,只有第一次才同步創建了以後就沒必要了。
public class SingletonDemo03 { private static SingletonDemo03 instance = null; public static SingletonDemo03 getInstance() { if (instance == null) { SingletonDemo03 sc; synchronized (SingletonDemo03.class) { sc = instance; if (sc == null) { synchronized (SingletonDemo03.class) { if(sc == null) { sc = new SingletonDemo03(); } } instance = sc; } } } return instance; } private SingletonDemo03() { } }
4.4:靜態內部類式(理解)
特點:線程安全、調用效率高;但是,可以延時載入
將實例化操作放到靜態內部類中,外部類沒有static,所以,在初始化類的時候,不會立即去載入靜態內部類,當然也不會去實例化對象,達到了延遲載入的特性。
只有在調用了getInstance()方法的時候,才會去載入靜態內部類。載入類的時候,線程是安全的。
instance是static final修飾的,保證了全局唯一性,即單例性。
兼備併發高效率調用和延遲載入特性。
public class SingletonDemo04 { private static class SingletonClassInstance { private static final SingletonDemo04 instance = new SingletonDemo04(); } public static SingletonDemo04 getInstance() { return SingletonClassInstance.instance; } private SingletonDemo04() { } }
4.5:枚舉單例(理解)
特點:線程安全、調用效率高,不能延時載入
枚舉本身就是單例模式。由JVM從根本上提供保障!避免通過反射和反序列化的漏洞!
public enum SingletonDemo05 { /** * 定義一個枚舉的元素,它就代表了Singleton 的一個實例。 */ INSTANCE; /** * 單例可以有自己的操作 */ public void singletonOperation(){ // 功能處理 } }
第五章:通過反射和反序列化破解單例模式(除枚舉單例)
5.1:通過反射破解單例模式(除枚舉單例)
//通過反射的方式直接調用私有構造器 Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.bjsxt.singleton.SingletonDemo6"); Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null); c.setAccessible(true); SingletonDemo6 s3 = c.newInstance(); SingletonDemo6 s4 = c.newInstance(); System.out.println(s3); System.out.println(s4);
如何避免反射破解單例?
通過在構造方法中手動拋出異常
private SingletonDemo6(){ //私有化構造器 if(instance!=null){ throw new RuntimeException(); } }
5.2:通過反序列化破解單例模式(除枚舉單例)
//通過反序列化的方式構造多個對象 FileOutputStream fos = new FileOutputStream("d:/a.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.close(); fos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt")); SingletonDemo6 s3 = (SingletonDemo6) ois.readObject(); System.out.println(s3);
如何避免反序列化破解單例模式?
表示:在反序列化的時候,如果已經定義了readResolver()方法,則直接返回此方法指定的對象,而不需要單獨再創建新對象。
private Object readResolve() throws ObjectStreamException { return instance; }
第六章:五種單例模式的效率問題
6.1:五種單例模式在多線程環境下的測試效率(相對效率)
CountDownLatch類:
同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或者多個線程一直等待。
countDown():當前線程調用此方法,則計數減一,減一放在finally中執行
await():調用此方法會一直阻塞當前線程,直到計數器為0;
public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); int threadNum = 10; final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for(int i=0;i<threadNum;i++){ new Thread(new Runnable() { @Override public void run() { for(int i=0;i<1000000;i++){ // Object o = SingletonDemo4.getInstance(); Object o = SingletonDemo5.INSTANCE; } countDownLatch.countDown(); } }).start(); } countDownLatch.await(); //main線程阻塞,直到計數器變為0,才會繼續往下執行! long end = System.currentTimeMillis(); System.out.println("總耗時:"+(end-start)); }
第七章:五種單例模式的選擇
(1)單例對象,占用資源少,不需要延遲載入
枚舉式優於餓漢式
(2)單例對象,占用資源大,需要延時載入
靜態內部類式優於懶漢式