枚舉單例模式:
關于單例模式的實現有很多種,網上也分析了如今實現單利模式最好用枚舉,好處不外乎三點:1.線程安全 2.不會因為序列化而產生新實例 3.防止反射攻擊
1.線程安全
下面這段代碼就是聲明枚舉實例的通常做法,它可能還包含實例變量和實例方法,但是為了簡單起見,我并沒有使用這些東西,僅僅需要小心的是如果你正在使用實例方法,那么你需要確保線程安全(如果它影響到其他對象的狀態的話)。默認枚舉實例的創建是線程安全的,但是在枚舉中的其他任何方法由程序員自己負責。
3.防止反射攻擊
反射攻擊,我有自己試著反射攻擊了以下,不過報錯了...看了下方的反編譯類源碼,明白了,因為單例類的修飾是abstract的,所以沒法實例化。(解決)
靜態內部類:
關于單例模式的實現有很多種,網上也分析了如今實現單利模式最好用枚舉,好處不外乎三點:1.線程安全 2.不會因為序列化而產生新實例 3.防止反射攻擊
1.線程安全
下面這段代碼就是聲明枚舉實例的通常做法,它可能還包含實例變量和實例方法,但是為了簡單起見,我并沒有使用這些東西,僅僅需要小心的是如果你正在使用實例方法,那么你需要確保線程安全(如果它影響到其他對象的狀態的話)。默認枚舉實例的創建是線程安全的,但是在枚舉中的其他任何方法由程序員自己負責。
關于線程安全的保證,其實是通過類加載機制來保證的,我們看看INSTANCE的實例化時機,是在static塊中,JVM加載類的過程顯然是線程安全的。
static {};
Code:
0: new #12; //class com/abin/lee/spring/util/Singleton$1
3: dup
4: ldc #14; //String INSTANCE
6: iconst_0
7: invokespecial #15; //Method com/abin/lee/spring/util/Singleton$1."<init>":(Ljava/lang/String;I)V
10: putstatic #19; //Field INSTANCE:Lcom/abin/lee/spring/util/Singleton;
13: iconst_1
14: anewarray #1; //class com/abin/lee/spring/util/Singleton
17: dup
18: iconst_0
19: getstatic #19; //Field INSTANCE:Lcom/abin/lee/spring/util/Singleton;
22: aastore
23: putstatic #21; //Field ENUM$VALUES:[Lcom/abin/lee/spring/util/Singleton;
26: return
線程安全,從反編譯后的類源碼中可以看出也是通過類加載機制保證的,應該是這樣吧
2.不會因為序列化而產生新實例枚舉自己處理序列化
傳統單例存在的另外一個問題是一旦你實現了序列化接口,那么它們不再保持單例了,因為readObject()方法一直返回一個新的對象就像java的構造方法一樣,你可以通過使用readResolve()方法來避免此事發生,看下面的例子://readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
這樣甚至還可以更復雜,如果你的單例類維持了其他對象的狀態的話,因此你需要使他們成為transient的對象。但是枚舉單例,JVM對序列化有保證。優點:不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象
反射攻擊,我有自己試著反射攻擊了以下,不過報錯了...看了下方的反編譯類源碼,明白了,因為單例類的修飾是abstract的,所以沒法實例化。(解決)
靜態內部類:
// Correct lazy initialization in Java
@ThreadSafe
class Foo {
private static class HelperHolder {
public static Helper helper = new Helper();
}
public static Helper getHelper() {
return HelperHolder.helper;
}
}
它利用了內部靜態類只有在被引用的時候才會被加載的規律。
這樣一來,一旦內部的HelperHolder被引用了,它就會首先被JVM加載,進行該類的靜態域的初始化,從而使得Helper這一單例類被初始化。它之所以是線程安全的,也是托了JVM的福,因為JVM對于類的加載這一過程是線程安全的。