1.定義
單例模式是確保全局只有一個實例悲幅,并且提供一個全局的訪問點煎饼,屬于創(chuàng)建型模式廉嚼,典型應(yīng)用就是枚舉
2.餓漢式單例模式
餓漢式單例模式在類加載的時候就已經(jīng)創(chuàng)建,屬于線程安全模式告组,因為那個時候還沒有線程,在線程出現(xiàn)以前就已經(jīng)實例化了癌佩,代碼:
/**
* 優(yōu)點:執(zhí)行效率高木缝,性能高,沒有任何的鎖
* 缺點:某些情況下围辙,可能會造成內(nèi)存浪費
* create by yufeng on 2021/7/3 21:00
*/
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
/**
* 靜態(tài)代碼塊的形式
* create by yufeng on 2021/7/3 21:00
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
3.懶漢式單例模式
為了餓漢模式帶來的內(nèi)存浪費問題我碟,就弄了個懶漢模式,因為只有在被用到的時候才會被實例化姚建,所以就避免了內(nèi)存浪費的問題
/**
* 優(yōu)點:節(jié)省了內(nèi)存,線程安全
* 缺點:性能低
* create by yufeng on 2021/7/3 21:12
*/
public class LazySimpleSingletion {
private static LazySimpleSingletion instance;
private LazySimpleSingletion(){}
public synchronized static LazySimpleSingletion getInstance(){
if(instance == null){
instance = new LazySimpleSingletion();
}
return instance;
}
}
為了解決上面性能低的問題矫俺,用雙重檢查鎖的方法:
/**
* 優(yōu)點:性能高了,線程安全了
* 缺點:可讀性難度加大掸冤,不夠優(yōu)雅
* create by yufeng on 2021/7/3 21:12
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//檢查是否要阻塞
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//檢查是否要重新創(chuàng)建實例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
//指令重排序的問題
}
}
}
return instance;
}
}
但是第二種寫法還是有鎖的問題厘托,存在性能的問題,這個時候就用內(nèi)部類的形式來解決
/*
兼顧了餓漢模式的浪費內(nèi)存稿湿,和懶漢模式鎖性能的問題
create by yufeng on 2021/7/3 21:12
*/
public class LazyInnerClassSingleton {
// 使用的時候默認先加載內(nèi)部類
private LazyInnerClassSingleton(){
}
private static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
// 默認不加載
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
但是上面的方法還是可以通過反射進行破壞單例模式铅匹,所以在上面的基礎(chǔ)實例化前加個校驗
/*
ClassPath : LazyStaticInnerClassSingleton.class
LazyStaticInnerClassSingleton$LazyHolder.class
優(yōu)點:寫法優(yōu)雅,利用了Java本身語法特點饺藤,性能高包斑,避免了內(nèi)存浪費,不能被反射破壞
缺點:不優(yōu)雅
create by yufeng on 2021/7/3 21:12
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允許非法訪問");
}
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
4.序列化
序列化和反序列化后,單例模式會被破壞涕俗,因為反序列化重新開辟內(nèi)存重新創(chuàng)建對象罗丰,違背了單例的初衷
/**
* create by yufeng on 2021/7/3 21:30
*/
public class SeriableSingleton implements Serializable {
//序列化
//把內(nèi)存中對象的狀態(tài)轉(zhuǎn)換為字節(jié)碼的形式
//把字節(jié)碼通過IO輸出流,寫到磁盤上
//永久保存下來咽袜,持久化
//反序列化
//將持久化的字節(jié)碼內(nèi)容丸卷,通過IO輸入流讀到內(nèi)存中來
//轉(zhuǎn)化成一個Java對象
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){ return INSTANCE;}
}
通過jdk源碼分析我們可以看到readResolve()方法雖然解決了序列化破壞單例的問題,但是實際上是實例化了2次询刹,只不過新創(chuàng)建的對象沒有被返回而已谜嫉,如果該動作頻率加快內(nèi)存分配開銷也會加大。注冊式模式會解決該問題
5.注冊式單例模式
注冊式單例模式凹联,也叫登記式單例模式沐兰,每一個實例都會登記到某一個地方,使用唯一的標識獲取實例蔽挠,注冊式單例模式有2中:枚舉住闯、容器
5.1枚舉式單例模式
類似餓漢模式瓜浸,類加載的是就會實例化,也不會被序列化破壞比原,因為枚舉式用過類名和類對象找到唯一對象插佛,所以不能被加載多次,也不會通過反射破壞量窘,因為沒無參構(gòu)造雇寇,只有一個protected類型的構(gòu)造方法,在newInstance() 時有個強制判斷蚌铜,如果是枚舉那就直接拋出異常锨侯,跟靜態(tài)內(nèi)部類類似的處理。
/**
* create by yufeng on 2021/7/3 22:30
*/
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
5.2容器式單例模式
枚舉式單例跟餓漢模式差不多冬殃,都會加載的時候就進行了實例化囚痴,不適合大量創(chuàng)建單例對象的場景,所以就有了容器式單例模式
/**
* create by yufeng on 2021/7/3 22:30
*/
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className){
Object instance = null;
if(!ioc.containsKey(className)){
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else{
return ioc.get(className);
}
}
}
容器式單例模式適用于需要大量創(chuàng)建單例模式對象的場景审葬,但是不是線程安全的