Singleton Pattern 獨體模式 [Design Pattern in Java]

Posted by Kubeguts on 2020-07-26

每次引用類別都只會產出相同的物件

適合用在: 執行緒池(thread pool),快取區(cache),對話盒、處理對話設定和登錄的物件,和驅動程式溝通的物件。

作法

假如有一個類別叫做MyClass,現在要讓MyClass變成獨體模式,讓外面的類別只能透過 MyClass.getInstance() 取得MyClass物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyClass {
// 設定成private與static,不能給外面類別存取
private static MyClass uniqueInstance;

// 將建構式設定成 private,這樣就可以避免直接被類別 new出來
private MyClass() {}

// 設置成static,使外面類別可以直接存取MyClass.getInstance方法
public static MyClass getInstance() {
// 若第一次取得MyClass, new 出MyClass();
if(uniqueInstance == null) {
uniqueInstance = new MyClass();
}
return uniqueInstance;
}
}

其他類別要取用MyClass

1
2
3
4
5
6
7
8
9
10
public static void main(String args[]) {
// 1. 第一次取用getInstance
Myclass instance = MyClass.getInstance();
// 2. 第二次取用getInstance時,MyClass的uniqueInstance已經有值了,得到與上面instance一樣的object
Myclass instance1 = MyClass.getInstance();

if(instance == instance1) {
System.out.println("instance and instance1 are the same");
}
}

多執行緒下獨體模式會遇到的狀況

假如有兩個thread(thread 1和thread 2),同時要跟MyClass取得物件,但會面臨thread1和thread2會取得不同的MyClass物件,情況如下:

解法一:只要把getInstance()給同步化 (會有效能不佳的狀況,可能造成效率下降100倍)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyClass {
private static MyClass uniqueInstance;

private MyClass() {}

// 設定成synchronized,就可以讓執行緒依序進入getInstance內
public static synchronized MyClass getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new MyClass();
}
return uniqueInstance;
}
}

但我們只需要第一次進入getInstance才進行同步化就好,不然其他次要取得uniqueInstnace都得變成同步的方式,造成取用Instance的緩慢

解法二:率先建立實體,不要等到有人呼叫getInstance才new出MyClass實體

1
2
3
4
5
6
7
8
9
10
11
public class MyClass {
// 直接初始話MyClass(),並設成private
private static MyClass uniqueInstnace = new MyClass();

private MyClass() {}

public static MyClass getInstance() {
return uniqueInstance;
}

}

此作法依賴JVM載入此類別時,馬上建立此唯一的獨體物件,JVM保證在任何執行緒存取uniqueInstnace靜態變數之前,一定先建立此實體

解法三:利用 “雙重檢查上鎖” 在getInstance()中減少使用同步化

利用雙重檢查上鎖,首先檢查是否實體已經建立了,
若沒有,“才”進行同步化,如此一來只有第一次進入getInstance才同步化,才是我們所想要的。

public class MyClass {
    // volatile為Java 6之後才有的關鍵字,能夠使執行緒們取得相同的uniqueInstance
    private volatile static MyClass uniqueInstance;
    
    private MyClass() {}
    
    public static MyClass getInstance() {
        // 只有第一次才徹底執行以下程式碼 
        // 當執行緒遇到被宣告成volatile的uniqueInstnace,會變得謹慎
        if(uniqueInstance == null) {
            synchronized(MyClass.class) {
                // 再檢查一次,若為null則new MyClass();
                if(uniqueInstance == null) {
                    uniqueInstance = new MyClass();
                }
            }
        }
        return uniqueInstance;
    }

}