1、餓漢式單例
它是在類加載的時候就立即初始化,並且創建單例對象
優點:沒有加任何的鎖、執行效率比較高,在用戶體驗上來說,比懶漢式更好
缺點:類加載的時候就初始化,不管你用還是不用,我都佔着空間,浪費了內存,有可能佔着茅坑不拉屎
絕對線程安全,在線程還沒出現以前就是實例化了,不可能存在訪問安全問題
注意代碼中的:readResolve方法及註釋內容
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() {}
//提供全局訪問點
public static HungrySingleton getInstance() {
return hungrySingleton;
}
//防止通過序列化和反序列化的方式破壞單例
private Object readResolve(){
return INSTANCE;
}
}
一上來就把單例對象創建出來了,要用的時候直接返回即可,這種可以說是單例模式中最簡單的一種實現方式。但是問題也比較明顯。單例在還沒有使用到的時候,初始化就已經完成了。也就是說,如果程序從頭到位都沒用使用這個單例的話,單例的對象還是會創建。這就造成了不必要的資源浪費。所以不推薦這種實現方式。
1.1 餓漢式靜態塊單例
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
2.懶漢式單例(在外部需要使用的時候才進行實例化)
public class LazySimpleSingleton {
//構造器私有化
private LazySimpleSingleton(){}
//靜態塊,公共內存區域
private static LazySimpleSingleton lazy = null;
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
3.Double CheckLock實現單例:DCL也就是雙重鎖判斷機制(由於JVM底層模型原因,偶爾會出問題,不建議使用):
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if(lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazy == null){
lazy = new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}
4.靜態內部類實現模式(線程安全,調用效率高,可以延時加載)
public class LazyInnerClassSingleton {
//默認使用LazyInnerClassSingleton的時候,會先初始化內部類
//如果沒使用的話,內部類是不加載的
private LazyInnerClassSingleton(){
//加if判斷,是爲了防止通過反射的方式獲取實例,而造成單例被破壞。也就是防止反射攻擊
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允許創建多個實例");
}
}
//每一個關鍵字都不是多餘的
//static 是爲了使單例的空間共享
//final 保證這個方法不會被重寫,重載
public static final LazyInnerClassSingleton getInstance(){
//在返回結果以前,一定會先加載內部類
return LazyHolder.LAZY;
}
//默認不加載
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
5.枚舉類(線程安全,調用效率高,不能延時加載,JDK層面天然的防止反射和反序列化調用)
JDK層面就不允許反射方式ENUM創建對象,可以查看jdk源碼
枚舉的單例模式使用我們的單例更加的優雅
public enum SingletonDemo5 {
//枚舉元素本身就是單例
INSTANCE;
//添加自己需要的操作
public void singletonOperation(){
}
}
6.註冊式單例
註冊式單例(容器式),保證線程內部的全局唯一,使用場景:多數據源動態切換。
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
單例模式總結:
1,私有化構造器
2,保證線程安全
3,延遲加載
4,防止序列化和反序列化破壞單例
5,防禦反射攻擊單例
缺點:1,沒有接口擴展困難;2,如果要擴展單例對象,只有修改代碼,沒有其他途徑
如何選用:
-單例對象 佔用資源少,不需要延時加載,枚舉 好於 餓漢
-單例對象 佔用資源多,需要延時加載,靜態內部類 好於 懶漢式