3.[研磨設計模式筆記]單例模式

餘二五發表於2017-11-16

1.定義

保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

2.解決問題

——讀取配置檔案的內容

不用模式的解決方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class AppConfig {
    private String parameterA;
    private String parameterB;
    public String getParameterA() {
        return parameterA;
    }
    public String getParameterB() {
        return parameterB;
    }
    public AppConfig() {
        readConfig();
    }
    private void readConfig() {
        Properties p = new Properties();
        InputStream in = null;
        try {
            in = AppConfig.class.getResourceAsStream(
                        “AppConfig.properties”);
            p.load(in);
            this.paramterA = p.getProperty(“paramA”);
            this.paramterB = p.getProperty(“paramB”);
        catch(Exception e) {
        finally {
            try {
                in.close();
            catch(Exception e) {
            }
        }
    }
}
public class Client {
    public static void main(String[] args) {
        AppConfig Config = new AppConfig();
        String paramA = config.getParameterA();
        String paramB = config.getParameterB();
    }
}

使用單例模式來解決問題

(餓漢式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class AppConfig {
    private static AppConfig instance = new AppConfig();
    public static AppConfig() getInstance() {
        return instance;
    }
    private String parameterA;
    private String parameterB;
    public String getParameterA() {
        return parameterA;
    }
    public String getParameterB() {
        return parameterB;
    }
    private AppConfig() {
        readConfig();
    }
    private void readConfig() {
        Properties p = new Properties();
        InputStream in = null;
        try {
            in = AppConfig.class.getResourceAsStream(
                        “AppConfig.properties”);
            p.load(in);
            this.paramterA = p.getProperty(“paramA”);
            this.paramterB = p.getProperty(“paramB”);
        catch(Exception e) {
        finally {
            try {
                in.close();
            catch(Exception e) {
            }
        }
    }
}
public class Client {
    public static void main(String[] args) {
        AppConfig Config = AppConfig.getInstance();
        String paramA = config.getParameterA();
        String paramB = config.getParameterB();
    }
}


3.模式講解

在不用模式的解決方案中,客戶端是通過new一個AppConfig的例項來得到一個操作配置檔案內容的物件。如果在系統執行中,有很多地方都需要使用配置檔案的內容,也就會有很多地方都建立AppConfig物件的例項。這樣系統會同時存在多份配置檔案的內容,會嚴重浪費記憶體資源。

解決思路

分析上面的問題,一個類能夠/被建立多個例項,問題在於/類的構造方法是公開的,也就是可以/讓類的外部/通過構造方法建立多個例項。要控制一個類/只被建立一個例項,那就是把類的建構函式/私有化,然後由這個類來/提供外部訪問/類例項的方法,這就是單例模式的實現方式。

單例模式使用來保證這個類在執行期間只會被建立一個類例項,另外,單例模式還提供一個全域性唯一訪問這個類例項的訪問點,就是getInstance方法。

示例程式碼

(懶漢式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {
    //4:定義一個變數來儲存建立好的類例項
    //5:因為這個變數要在靜態方法中使用,所以需要加上static修飾
    private static Singleton instance = null;
    //1:私有化構造方法,好在內部控制建立例項的數目
    private Singleton() {}
    //2:定義一個方法來為客戶端提供類例項
    //3:這個方法需要定義成類方法(靜態方法),也就是要加static
    public static synchronized Singleton getInstance() {
        //6:判斷儲存例項的變數是否有值
        if(instance == null) {
            //6.1:如果沒有,就建立一個類例項,並把賦值給儲存例項的變數
            instance = new Singleton();
        }
        //6.2:如果有值,那就直接使用
        return instance;
    }
}

(餓漢式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Java中static的特性:
1.static變數在類載入的時候進行初始化。
2.多個例項的static變數會共享同一塊記憶體區域。
* 在Java中static變數只會被初始化一次,就是在類載入的時候,而且多個例項都會共享這個記憶體空間。
/*
public class Singleton {
    //4:定義一個變數來儲存建立好的類例項
    //直接在這裡建立類例項,只能建立一次
    private static Singleton instance = new Singleton();
    //1:私有化構造方法,好在內部控制建立例項的數目
    private Singleton() {}
    //2:定義一個方法來為客戶端提供類例項
    //3:這個方法需要定義成類方法(靜態方法),也就是要加static
    public static Singleton getInstance() {
        //5:直接使用以建立好的例項
        return instance;
    }
}

在懶漢式方案裡,強制加上static,並沒有使用static的特性;而在餓漢式方案裡,是主動加上static,使用了static的特性。

單例模式的懶漢式實現體現了延遲載入的思想,即就是一開始不要載入資源或資料,一直等,等到要使用這個資源或資料才載入,也成Lazy Load。

單例模式的懶漢式實現還體現了快取的思想,即當某些資源或資料被頻繁地使用,而這些資源或資料儲存在系統外部(如資料庫、硬碟檔案等),每次操作這些資料的時候都要去獲取。通過把這些資料快取到記憶體中,每次操作先到記憶體裡面找,如果有就直接使用,如果沒有就去獲取,並設定到快取中,下一次訪問的時候就可以直接從記憶體中獲取。

應用範圍

Java裡面實現單例的是一個虛擬機器範圍,虛擬機器在通過自己的ClassLoader裝載餓漢式實現單例類就會建立一個類的例項。

單例模式呼叫示意圖

(懶漢式)

124227675.png

(餓漢式)

124220502.png

單例模式的優缺點

1.時間和空間

懶漢式是典型的時間換空間;

餓漢式是典型的空間換時間。

2.執行緒安全

不加同步的懶漢式是執行緒不安全的;

餓漢式是執行緒安全的。

雙重檢查加鎖

指的是:並不是每次進入getInstance方法都需要同步,而是先不同步,進入方法後,先檢查例項是否存在,如果不存在,就在同步的情況下建立一個例項,這是第二重檢查。這樣,就只需要同步一次,從而減少多次在同步情況下進行判斷浪費的時間。

雙重檢查加鎖的實現使用了一個關鍵字volatile,意思是:被volatile修飾的變數的值,將不會被本地執行緒快取,所有對該變數的讀寫都是直接操作共享記憶體的,從而確保多個執行緒能正確的處理該變數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {
    //對儲存例項的變數增加volatile的修飾
    private volatile static Singleton instance = null;
         
    private Singleton() {}
         
    public static Singleton getInstance() {
        //先檢查例項是否存在,如果不存在才進入下面的同步塊
        if(instance == null) {
            //同步塊,執行緒安全地建立例項
            synchronized(Singleton.class) {
                //再次檢查例項是否存在,如果不存在才真正地建立例項
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}


4.思考

單例模式的本質:控制例項數目

何時選用單例模式:

如果當:需要控制一個類例項只能有一個,且客戶只能從一個全域性訪問點控制它時,選用單例模式。

本文轉自 LinkedKeeper 51CTO部落格,原文連結:http://blog.51cto.com/sauron/1224147,如需轉載請自行聯絡原作者


相關文章