曾經天真的覺得單例只有懶漢和餓漢兩種!原來單例模式還能被破解!!!

01-單例設計模式

第一章:單例模式核心做用

(1)保證一個類只能有一個實例(一個對象)spring

(2)而且提供一個供外界訪問該實例的全局訪問點數據庫

第二章:常見應用場景

(1)windows的任務管理器、回收站編程

(2)項目中,讀取配置文件的類,通常只有一個對象。不必每次使用配置文件的數據都要new一個對象去讀取windows

(3)網站的計數器,通常採用單例模式設計,不然沒法作到同步設計模式

(4)數據庫的鏈接池設計,通常使用單例模式設計安全

(5)在spring中,每一個bean默認就是單例模式,這樣作的優勢是spring容器方便管理多線程

(6)在servlet編程中,每一個Servlet也是單例模式併發

第三章:單例模式的優勢

(1)減小系統性能的開銷:當一個對象的產生須要比較多的資源時,如須要讀取配置、產生其餘依賴對象時,則能夠經過在應用啓動時直接產生一個單例對象,而後永久存留在內存中的方式來解決。ide

(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)單例對象,佔用資源大,須要延時加載

靜態內部類式優於懶漢式

相關文章
相關標籤/搜索