1.定義
單例模式是Java中比較常見的創建型設計模式,他的核心是確保一個類在任何情況下都絕對只有一個實例,並提供一個全局訪問點。
如何確保一個類在任何情況下都絕對只有一個實例?是單例模式設計的主要實現方向。下面介紹下單例模式的主要實現方法
2.餓漢式
/**
* 餓漢式
* 在類加載時直接實例化單例
* 缺點,類加載時就創建實例,浪費空間
*/
public class HungryMan {
/**
* 類加載時創建初始化
*/
private static final HungryMan INSTANCE = new HungryMan();
/**
* 私有化構造方法
*/
private HungryMan(){}
/**
* 提供全局訪問點
* @return 單例對象
*/
public static HungryMan getInstance() {
return INSTANCE;
}
}
3.懶漢式
/**
* 懶漢式 未加鎖 存在線程安全問題
* 在調用時才實例化單例
*/
public class LazyMan {
/**
* 類加載時不初始化實例
*/
private static LazyMan INSTANCE;
/**
* 私有化構造方法
*/
private LazyMan() {
}
public static synchronized LazyMan getInstance() {
//這裏需要辦法保證只有一個實例
if (INSTANCE == null)
INSTANCE = new LazyMan();
return INSTANCE;
}
}
改進版
/**
* 懶漢式 雙重檢查鎖單例
* 爲了保證單例的線程安全,使用雙重加鎖的方式
* 問題:加鎖所造成的性能問題
*/
public class LazyMan2 {
/**
* 類加載時不初始化實例
* <p>
* volatile 可以防止指令重排序問題
*/
private volatile static LazyMan2 INSTANCE;
/**
* 私有化構造方法
*/
private LazyMan2() {
}
/**
* 等到調用時初始化單例實例
* 如果沒有 synchronized關鍵字,容易出現線程安全問題,因此需要添加synchronized進行同步
*
* @return 單例對象
*/
public static LazyMan2 getInstance() {
// 1.如果INSTANCE不爲null,則不需要獲取鎖,提高性能
if (INSTANCE == null)
synchronized (LazyMan2.class) {
// 2.爲了避免多線程解鎖後重復創建對象
if (INSTANCE == null)
/*
* CPU執行時會轉換成JVM指令執行
*
* 1.分配內存給這個對象
* 2.初始化對象
* 3.將初始化後的對象和內存地址建立關聯,賦值
* 第二步和第三步可能調換順序(賦值在創建對象之前),線程B在線程A賦值完時判斷instance就不爲null了,此時B拿到的將是一個沒有初始化完成的半成品。
* 所以會在單例對象添加volatile修飾
*/
INSTANCE = new LazyMan2();
}
return INSTANCE;
}
}
4.註冊式單例模式
4.1 容器式單例
/**
* 容器式單例
*
* 容器式單例都屬於註冊式單例模式,其核心思想是:
* 在使用時,先去容器中查找,如果找到了,就將查出來的對象返回
* 否則,實例化,然後轉載到容器中,最後將實例化的對象返回
*/
public class ContainerSingleton {
/**
* 單例容器
* 存在線程安全問題,因此使用ConcurrentHashMap
*/
private static final Map<String, Object> ioc = new ConcurrentHashMap<>();
/**
* 私有化構造函數
*/
private ContainerSingleton() {
}
/**
* 容器式單例模式
*
* @param key 獲取單例的key
* @return 單例對象
*/
public static Object getBean(String key) {
if (ioc.containsKey(key)) {//如果有就取出返回
return ioc.get(key);
}
//如果沒有,新建-裝載-返回
try {
Object instance = Class.forName(key).newInstance();
ioc.put(key, instance);
return instance;
} catch (Exception e) {
e.printStackTrace();
}
//裝載異常, 返回空
return null;
}
}
3.2 枚舉式單例(強烈推薦)
/**
* 枚舉單例模式
* 屬於裝載類單例模式
*
* 在調用時,先查詢容器中是否有此對象的實例,有就取出直接返回,否則新建一個實例並且將其裝載到容器中
* 體現在Enum.valueOf((Class)cl, name);這個方法上
*/
public enum EnumSingleton {
INSTANCE;
/**
* 用來擴展的對象
*/
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
/**
* 提供全局訪問點
*
* @return 單例對象
*/
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
5.簡單總結
- 私有化構造器
- 保證線程安全
- 延遲加載