使用場景
實際的開發中,為了避免創建多個對象消耗過多的資源,或者某個類的對象只能有一個,所以就需要使用單例模式來確保某個類只能對外提供一個對象。
特點
- 類的構造函數一般用private修飾,不對外公開
- 一般通過一個靜態方法返回單例對象
- 必須保證線程安全,即在多線程場景下能確保只有一個單例對象
實現方式
1、懶漢單例模式
public class Singleton{
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
懶漢單例模式只有在第一次使用時才會初始化單例,一定程度上能節約資源,但反應會稍慢;通過synchronized關鍵字,保證了在多線程情況下單例的唯一性,但是在單例被第一次初始化后,再調用getInstance()方法還需要進行同步操作,這樣會造成不必的系統開銷。
2、雙重檢查鎖定單例模式(Double Check Lock)
public class Singleton {
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;
}
}
在getInstance()中,首先對instance進行非空判斷,避免多余的同步,這也解決了懶漢單例模式中每次同步的問題,接下來如果instance為空則創建其實例,當然這一步需要保證同步操作。
但這里有個隱藏問題,注意instance = new Singleton();
這行代碼,它的執行可以分解為第三個步驟:(1)為instance實例分配內存。(2)執行Singleton構造函數來初始化instance。(3)將instance指向分配的內存。
但在JDK1.5前,上邊的(2)(3)無法保證按順序執行,如果按(1)(3)(2)順序,假如A線程執行完(3),(2)未執行就被切換到B線程,因為步驟(3)已經在A線程執行,則B線程直接取走了認為非空instance,這就導致雙重檢查鎖定的判斷失效。
在JDK1.5后,只要這樣聲明instance實:private volatile static Singleton instance;
即添加volatile修飾符,這樣就可以保證instance每次都從主內存讀取,避免了上邊的問題,但會略影響性能。這種單例模式也是在第一次執行getInstance()時創建單例,但第一次反映稍慢。
這種方式目前使用的較多。
3、靜態內部類單例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
這種方式只有在的第一次調用getInstance()方法時,虛擬機才會加載SingletonHolder類,并初始化instance實例,即保證了線程同步,也能保證單例的唯一性,相對雙重檢查鎖定單例模式簡單了許多,推薦使用這種方式來實現單例模式。
4、容器單例模式
public class SingletonManager {
private static Map<String, Object> instanceMap = new HashMap<>();
private SingletonManager() {
}
public static void addInstance(String key, Object instance) {
if (!instanceMap.containsKey(key)) {
instanceMap.put(key, instance);
}
}
public Object getInstance(String key) {
return instanceMap.get(key);
}
}
采用Map集合管理對象的實例,保證實例的唯一性,這種方式多用于管理多種類的實例場景,同時你的類并不一定需要實現單例機制,因為SingletonManager可以解決這個問題。你只需在初始化時創建對應類的實例并調用addInstance(String key, Object instance)
來進行保存,使用時調用getInstance(String key)
,即可根據key得到對應類的實例。
5、枚舉單例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
private enum SingletonEnum {
INSTANCE;
private Singleton instance;
SingletonEnum() {
instance = new Singleton();
}
public Singleton getInstance() {
return instance;
}
}
}
這種寫法相對最簡單,并且枚舉實例的創建是線程安全的,并且任何情況只有一個單例。