Singleton單例
Prerequisites:
Singleton 有很多中文翻譯,如∶ 獨體,單例
什麼是Singleton?Singleton確保一個類別只有一個實體。
何時用到Singleton,消耗系統資源的建立的物件,但又頻繁使用,只想只建立一次,不想頻繁的建立。
靜態變數建立Singleton
使用靜態只產生一次的特性,儲存物件,確保一個類別只有一個實體。
步驟如下:
- 把建構子變成私有
- 私有靜態變數來存放物件
- 提供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
優缺點分析
優點
static方法、static變數、static區塊、static靜態內部類別,是在JVM啟動時,ClassLoader類別載入器就已經先丟進Method area(Metaspace)的靜態資料區中,若靜態變數的類型是物件(如String),就會把「靜態變數」放在jvm的stack中,而變數指向的「物件」則放入jvm的heap(堆)的區域中。
以上這些動作只做一次,不管產生多少次物件,都只做一次。
缺點
不管有沒有使用到,都會產生一次物件,若沒使用到這個物件就會浪費記憶體位址去存放這個物件。
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;
}
}
靜態內部類別建立Singleton
1
2
3
4
5
6
7
8
9
10
11
12
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,一次只能有一個執行緒進入執行,執行完成後輪下一個執行緒執行此方法,若後面有1000個執行緒要執行這個方法,會造成執行緒排隊等待,執行效能緩慢,以下方法不推薦。
1
2
3
4
5
6
7
8
9
10
11
12
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),然後突然控制權輪到下一個執行緒來使用並建好instance,建完instance後,控制權又輪回前一個執行緒,它才剛執行完if (instance == null),要開始建立instance,然後就建立第2個instance成功了。
為了不讓人建立第2個物件,所以在同步synchronized區塊內加上第二次 if (instance == null)。
加快效率
第3個執行緒只要看到第一個 if (instance == null),此時instance已經被建立了,就會跳離這個if判斷式,然後就不用再執行synchronized區塊,也就是說synchronized區塊最多只被執行2次。
volatile
volatile中文是可見性
每個變數都有自己的cache(暫存空間),有變更不會立刻改變,好幾百個執行緒讀取這個變數,各個執行緒可能會先cache暫存起來,加上volatile,就是不讓各個執行緒把這個變數暫存,永遠重新讀取變數的值。