單例模式是比較簡單的設計模式。直接給出定義就明白了。
單例模式確保一個類只有一個實例,並提供一個全局訪問點
要保證一個類只有一個實例需要做到以下幾點:
- 私有類構造器,防止別的類直接用類構造器實例化實例
- 既然構造器私有了,那麼只有類本身能夠實例化。類需要提供一個公開的接口獲取實例
- 類本身需要保證實例的單一
類圖如下:
類怎麼創建單一的實例,有以下幾種辦法:
錯誤的示範
public class Singleton {
private static Singleton instance;
//私有的構造器
private Singleton() {}
//公開的訪問點
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上面的代碼看似符合單例模式,但是類本身無法保證只實例化一次實例。
在多線程的情況下,可能有多個線程得到 instance == null
爲true的結果。
因此單例模式比較重要的就是怎麼避免多線程重複實例化的問題。
使用 synchronized 同步
public class Singleton {
private static Singleton instance;
//私有的構造器
private Singleton() {}
//公開的訪問點,加上synchronized避免多線程同時進入方法中
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
比較簡單的方法,直接在實例方法上加synchronized
,兩個線程無法同時進入方法,自然不會重複實例化了。
不好的是線程的互相等待影響效率。
實例化靜態變量
public class Singleton {
//加載類時就創建對象,由jvm保證單例
private static Singleton instance = new Singleton();
//私有的構造器
private Singleton() {}
//公開的訪問點
public static Singleton getInstance() {
return instance;
}
}
靜態變量的實例jvm加載類時會自動創建,並且保證實例的唯一。
這個辦法不好的地方是,即使不需要這個實例它也會創建。
雙重檢查加鎖
public class Singleton {
//volatile保證不同線程之間的變量是一樣的
private volatile static Singleton instance;
//私有的構造器
private Singleton() {}
//公開的訪問點,沒有實例化的前提下加鎖
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
訪問點在沒有實例化的判斷之後加鎖,各個線程最多隻有第一次獲取實例的時候會被同步,提高了效率。
注意的是實例變量需要設置爲 volatile。保證各個線程看到的全局變量一致。否則第二次的檢查也有可能出現多線程錯誤。