#學習記錄(剛剛學習之後進行了簡單的總結)
github地址 https://github.com/gaoruiqiang2017/singleton.git
單例模式的定義
單例模式(Singleton Pattern)是指確保一個類在任何情況
下都絕對只有一個實例,並提供一個全局訪問點。
隱藏其所有的構造方法。
屬於創建型模式。
確保任何情況下都絕對只有一個實例,如 ServletContext、ServletConfig、ApplicationContext
餓漢式單例
public class HungrySingleton {
/*
* 餓漢式單例
* 它在類加載的時候就立即初始化,並且創建單例對象
* 優點:沒有加任何的鎖,執行效率比較高,用戶體驗上來說比懶漢式更好
* 缺點:類加載的時候就初始化,不管用或者不用,都佔着空間,浪費了內存
*
* 絕對線程安全,在線程還沒有出現之前就實例化了,不可能存在訪問安全問題
* 餓漢式適用在單例對象較少的情況
* */
private static final HungrySingleton hungetSingleton = new HungrySingleton();
//私有的無參構造
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungetSingleton;
}
}
還有種寫法
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungryStaticSinglrton;
//餓漢式靜態塊單例
static {
hungryStaticSinglrton = new HungryStaticSingleton();
}
private HungryStaticSingleton() {
}
public static HungryStaticSingleton getHungryStaticSinglrton() {
return hungryStaticSinglrton;
}
}
懶漢式單例
第一種寫法
public class LazySimpleSingleton {
/*
* 懶漢式單例
* 在外部需要使用的時候才進行實例化
* */
private LazySimpleSingleton() {
}
//靜態塊,公共內存區域
private static LazySimpleSingleton lazy = null;
/*
* 用synchronized 加鎖,在線程數量比較多情況下,如果CPU 分配壓力上升,會導致大批
* 量線程出現阻塞,從而導致程序運行性能大幅下降。
* */
public synchronized static LazySimpleSingleton getInstance() {
if (lazy == null) {
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
第二種寫法
public class LazyDoubleCheckSingleton {
private LazyDoubleCheckSingleton() {
}
//靜態塊,公共內存區域
//volatile:指令重排序
private volatile static LazyDoubleCheckSingleton lazy = null;
//雙重檢查鎖
public static LazyDoubleCheckSingleton getInstance() {
if (lazy == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazy == null) {
lazy = new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}
第三種寫法
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {
//反射可以破壞單例,需要在無參構造中加判斷
if (LazyHolder.lazy != null) {
throw new RuntimeException("不允許創建多個實例");
}
}
//static是爲了使單例的空間共享,保證這個方法不會被重寫,重載
public static final LazyInnerClassSingleton getInstance() {
//返回結果之前,一定會先加載內部類
return LazyHolder.lazy;
}
///默認使用LazyInnerClassGeneral 的時候,會先初始化內部類
//如果沒使用的話,內部類是不加載的
private static class LazyHolder {
private static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
}
//反序列化時會破壞單例
//反序列化將已經持久化的字節碼內容,轉換爲IO流
//通過IO流的讀取,進而將讀取的內容轉換爲Java對象
//在轉換過程中會重新創建對象new
//重寫 readResolve() 方法可以解決這個問題,但是只不過是覆蓋了反序列化出來的對象
//還是創建了倆次,發生在JVM層面,比較安全,但是內存依舊會有開銷
//之前反序列化出來的對象會被GC回收
private Object readResolve() {
return LazyHolder.lazy;
}
}
註冊式單例
將每一個實例都緩存到統一的容器中,使用唯一標識獲取實例
public enum EnumSingleton {
INSTANCE;
/*
* 枚舉式單例,也是註冊時單例中的一中
* 枚舉式單例在靜態代碼塊中就給INSTANCE 進行了賦值,是餓漢式單例的實現
* 枚舉類型其實通過類名和Class 對象類找到一個唯一的枚舉對象。因此,枚舉對
* 象不可能被類加載器加載多次,因此序列化破壞不了單例
* Constructor 的 newInstance()方法中做了強制性的判斷,如果修飾符是Modifier.ENUM 枚舉類型,
* 直接拋出異常,因此反射破壞不了單例
*
* */
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
public class ContainerSingleton {
private ContainerSingleton() {
}
//容器式單例,註冊式單例的一種
private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getBean(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
單例模式的優點:
在內存中只有一個實例,減少了內存開銷。
可以避免對資源的多重佔用。
設置全局訪問點,嚴格控制訪問。
單例模式的缺點:
沒有接口,擴展困難。
如果要擴展單例對象,只有修改代碼,沒有其他途徑。
總結
1、私有化構造器
2、保證線程安全
3、延遲加載
4、防止序列化和反序列化破壞單例
5、防禦反射攻擊單例