本文針對於單例模式中對象創建中的線程安全問題。主要以懶漢式,餓漢式,靜態內部類,枚舉類分析在調用時創建對象的線程安全問題。
1.餓漢式
類加載會導致該單實例對象被創建
// 問題1:爲什麼加 final
// 問題2:如果實現了序列化接口, 還要做什麼來防止反序列化破壞單例
public final class HungrySingleton implements Serializable {
// 問題3:爲什麼設置爲私有? 是否能防止反射創建新的實例?
private HungrySingleton(){}
// 問題4:這樣初始化是否能保證單例對象創建時的線程安全?
private static final HungrySingleton INSTANCE = new HungrySingleton();
// 問題5:爲什麼提供靜態方法而不是直接將 INSTANCE 設置爲 public, 說出你知道的理由
public static HungrySingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
問題回答:
- 用final修飾的類不能被擴展,也就是說不可能有子類。若被繼承子類可能修改父類的內部結構會破壞單例模式
- 重寫readResolve()並返回單例對象;readResolve() 讀取序列保存的變量值,導致在反序列化的時通過字節碼會重新生成實例導致單例被破壞
- 私有化創建方法導致外部不能夠調用構造方法直接創建實例;不能夠防止反射創建實例,Co
constructor.setAccessible(true);即可訪問私有構造函數 - 線程安全;成員變量是在類加載的時候完成初始化,類加載的階段有JVM保證線程安全
- 方法可以靈活封裝並且可以提供對泛型的支持(個人看法)
2.懶漢式
類加載不會導致該單實例對象被創建,而是首次使用該對象時纔會創建
private Singleton() { }
private static Singleton INSTANCE = null;
// 分析這裏的線程安全, 並說明有什麼缺點
public static synchronized Singleton getInstance() {
if( INSTANCE != null ){
return INSTANCE;
}
INSTANCE = new Singleton();
return INSTANCE;
}
問題:
synchronized同步代碼塊鎖範圍過大,導致調用初始化方法每次都需要加鎖解鎖,存在效率問題
3.枚舉類
// 問題1:枚舉單例是如何限制實例個數的
// 問題2:枚舉單例在創建時是否有併發問題
// 問題3:枚舉單例能否被反射破壞單例
// 問題4:枚舉單例能否被反序列化破壞單例
// 問題5:枚舉單例屬於懶漢式還是餓漢式
// 問題6:枚舉單例如果希望加入一些單例創建時的初始化邏輯該如何做
enum Singleton {
INSTANCE;
}
問題回答:
- 該答案通過字節碼來進行說明
public final enum com/lock/singleton/Singleton extends java/lang/Enum {
// compiled from: Singleton.java
// access flags 0x4019
public final static enum Lcom/lock/singleton/Singleton; INSTANCE
INSTANCE爲枚舉類中的靜態成員變量肯定是單實例
2.靜態成員變量在類加載器創建對象時已經由JVM創建完成,不存在併發問題
3.答案是不能的
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//用於判斷是否有Enum修飾
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
- 不能被序列化
- 在類加載時創建實例變量,屬於餓漢式
- 以下是我個人一些實現
public class EnumSingleton {
//私有化構造函數
private EnumSingleton() {
}
//定義一個靜態枚舉類
static enum EnumSingletonClass {
//創建一個枚舉對象,該對象天生爲單例
INSTANCE;
private EnumSingleton enumSingleton;
//私有化枚舉的構造函數
private EnumSingletonClass() {
enumSingleton = new EnumSingleton();
}
public EnumSingleton getInstance() {
return enumSingleton;
}
}
//對外暴露一個獲取對象的靜態方法
public static EnumSingleton getInstance() {
return EnumSingletonClass.INSTANCE.getInstance();
}
}
DCL( double-checking lock) 懶漢單例
public final class Singleton {
private Singleton() { }
// 問題1:解釋爲什麼要加 volatile ?
private static volatile Singleton INSTANCE = null;
// 問題2:對比實現3, 說出這樣做的意義
public static Singleton getInstance() {
if (INSTANCE != null) {
return INSTANCE;
}
synchronized (Singleton.class) {
// 問題3:爲什麼還要在這裏加爲空判斷, 之前不是判斷過了嗎
if (INSTANCE != null) { // t2
return INSTANCE;
}
INSTANCE = new Singleton();
return INSTANCE;
}
}
}
問題回答:
靜態內部類懶漢單例
public class StaticInnerSingleton {
private StaticInnerSingleton(){}
// 問題1:屬於懶漢式還是餓漢式
private static class LazyHolder{
static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
// 問題2:在創建時是否有併發問題
public static StaticInnerSingleton getInstance(){
return LazyHolder.INSTANCE;
}
}
問題回答:
- 1.懶漢式,因爲類加載屬於懶加載只有當使用的該類時纔會創建。
- 2.不會有併發問題,當外部調用getInstance,會觸發類加載並由JVM創建實例,所以保證了線程安全問題。
以上爲個人學習總結,若有不準備之處還望指正,共勉!