Singleton

Prerequisites:

本篇文章需要相當多大量知識,了解類別載入metadata靜態變數靜態區塊靜態內部類別final執行緒synchronized,擁有以上的知識,才能了解下面的內容。

Singleton 有很多中文翻譯,如∶ 獨體,單例

什麼是Singleton?Singleton是類別的靜態變數,而這個靜態變數是類別本身的物件。

而類別的變數只會在類別載入記憶體時,建立一次,確保不會被其它人建立多次。

何時用到Singleton,消耗系統資源的建立的物件,但又頻繁使用,只想只建立一次,不想頻繁的建立。

靜態變數建立Singleton

步驟如下:

  1. 把建構子變成私有。
  2. 私有靜態變數來存放本身類別的物件。
  3. 提供public靜態的方法,傳回物件。

為什麼建構子要變成私有,確保只有類別本身才可以呼叫建構子,其它類別都不能呼叫建構子建立物件。

1
2
3
4
5
6
7
8
9
10
class Singleton1 {
  // 1. 把建構子存取權限改成private
  private Singleton1() {}
  // 2. 私有靜態變數,存放物件
  private static Singleton1 instance = new Singleton1();
  // 3. 提供public static方法,傳回物件
  public static Singleton1 getInstance() {
    return instance;
  }
}

測試是否只產生一個物件,測試結果hashCode都是相同,證明不管產生幾次都為相同物件。

1
2
3
4
5
6
7
8
9
10
public class Test {
  public static void main(String[] args) {
    Singleton1 singleton1 = Singleton1.getInstance();
    Singleton1 singleton2 = Singleton1.getInstance();
    System.out.println("singleton1 = " + singleton1.hashCode());
    System.out.println("singleton2 = " + singleton2.hashCode());
    boolean result = (singleton1 == singleton2);
    System.out.println("singleton1 == singleton2 = " + result);
  }
}
singleton1 = 821270929
singleton2 = 821270929
singleton1 == singleton2 = true

Singleton寫在靜態區塊

靜態變數是在靜態區塊中才建立物件,跟之前的作法沒什麼不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton1 {
  // 1. 把建構子存取權限改成private
  private Singleton1() {}
  private static Singleton1 instance;
  // 2. 寫在靜態區塊
  static {
    instance = new Singleton1();
  }
  // 3. 提供public static方法,傳回物件
  public static Singleton1 getInstance() {
    return instance;
  }
}

優缺點分析

優點

static方法、static變數、static區塊,ClassLoader類別載入時就已經存放在metadata

靜態相關屬性、方法、變數,不管產生多少次物件,都只載入記憶體一次。

缺點

不管有沒有使用到,都會產生一次物件,若沒使用到這個物件就會浪費記憶體位址去存放這個物件。

如果singleton物件要消耗很多執行時間,可能要網路連線、資料庫連線、檔案載入,執行速度就會很慢。

靜態內部類別建立Singleton

為了解決先前提到的缺點,使用靜態內部類別,可以做到使用的時候,才會建立。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton1 {
  // 1. 把建構子存取權限改成private
  private Singleton1() {}
  // 靜態內部類別
  private static class InnerClz {
    // final代表不能再被修改
    private static final Singleton1 INSTANCE = new Singleton1();
  }
  // 2. 提供public static方法,傳回物件
  public static Singleton1 getInstance() {
    return InnerClz.INSTANCE;
  }
}

優點

  • 靜態只產生一次物件。
  • 使用到靜態內部類別才會被建立,要使用的時候才去建這個類別,才不會造成記憶體空間浪費。

synchronized加上雙重檢查

不使用靜態內部類別,使用靜態方法 + synchronized,也可以有靜態內部類別的效果(使用的時候才建立物件)。

未優化前(不推薦使用)

在靜態方法前加上synchronized,一次只能有一個執行緒進入靜態方法執行,執行完成後輪下一個執行緒執行此方法,若後面有1000個執行緒要執行這個方法,會造成執行緒排隊等待,執行效能緩慢,以下方法不推薦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton1 {
  // 1. 把建構子存取權限改成private
  private Singleton1() {}
  // 靜態變數
  private static Singleton1 instance;
  
  // 2. 提供public static方法,傳回物件
  public static synchronized Singleton1 getInstance() {
    if (instance == null) {
      instance = new Singleton1();
    }
    return instance;
  }
}

雙重檢查Double-Checked Locking

好幾百個執行緒進來要取得instance,確保只有一個執行緒建立一個instance。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton1 {
  // 1. 把建構子存取權限改成private
  private Singleton1() {}
  private static volatile Singleton1 instance;
  // 2. 提供public static方法,傳回物件
  public static Singleton1 getInstance() {
    if (instance == null) {
      synchronized(Singleton1.class) {
        if (instance == null) {
          instance = new Singleton1();
        }
      }
    }
    return instance;
  }
}

為什麼是用synchronized(Singleton1.class)?

Singleton1.class是Class類別的物件。

getInstance()是類別的靜態方法,同步鎖,鎖的是類別,所以使用synchronized(類別.class)

為什麼要檢查二次

為什麼要檢查二次 if (instance == null) ?

下面這個程式碼只有一次if (instance == null)

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton1 {
  private Singleton1() {}
  private static volatile Singleton1 instance;

  public static Singleton1 getInstance() {
    if (instance == null) {
      synchronized(Singleton1.class) {
          instance = new Singleton1();
      }
    }
    return instance;
  }
}

想像一下,有一個執行緒A執行完第一個if (instance == null),然後同步區塊有執行緒B使用中(尚未建立物件),所以執行緒A在同步區塊外面等待,然後在同步區塊中的執行緒B, 建立好物件instance,離開同步區塊,接著就輪到執行緒A進入同步區塊,接著就建立好第2個instance。

為了不讓人建立第2個物件,所以在同步synchronized區塊內加上第二次 if (instance == null)。

加快效率

第3個執行緒只要看到第一個 if (instance == null),此時instance已經被建立了,就會跳離這個if判斷式,然後就不用再執行synchronized區塊,也就是說synchronized區塊最多只被執行2次。

volatile

1
private static volatile Singleton1 instance;

volatile中文是可見性

每個變數都有自己的cache(暫存空間),有變更不會立刻改變,好幾百個執行緒讀取這個變數,各個執行緒可能會先cache暫存起來,加上volatile,就是不讓各個執行緒把這個變數暫存,永遠重新讀取變數的值。

results matching ""

    No results matching ""