Singleton
這裡,我将直接給出一個Singleton的簡單實作,因為我相信你已經有這方面的一些基礎了。我們姑且把這個版本叫做1.0版
// version 1.0 public class Singleton { private static Singleton singleton = null ; private Singleton() { } public static Singleton getInstance() { if (singleton== null ) { singleton= new Singleton(); } return singleton; } } |
在上面的執行個體中,我想說明下面幾個Singleton的特點:(下面這些東西可能是盡人皆知的,沒有什麼新鮮的)
- 私有(private)的構造函數,表明這個類是不可能形成執行個體了。這主要是怕這個類會有多個執行個體。
- 即然這個類是不可能形成執行個體,那麼,我們需要一個靜态的方式讓其形成執行個體:getInstance()。注意這個方法是在new自己,因為其可以通路私有的構造函數,是以他是可以保證執行個體被建立出來的。
- 在getInstance()中,先做判斷是否已形成執行個體,如果已形成則直接傳回,否則建立執行個體。
- 所形成的執行個體儲存在自己類中的私有成員中。
- 我們取執行個體時,隻需要使用Singleton.getInstance()就行了。
當然,如果你覺得知道了上面這些事情後就學成了,那得給你當頭棒喝一下了,事情遠遠沒有那麼簡單。
上面的這個程式存在比較嚴重的問題,因為是全局性的執行個體,是以,在多線程情況下,所有的全局共享的東西都會變得非常的危險,這個也一樣,在多線程情況下,如果多個線程同時調用getInstance()的話,那麼,可能會有多個程序同時通過 (singleton== null)的條件檢查,于是,多個執行個體就建立出來,并且很可能造成記憶體洩露問題。嗯,熟悉多線程的你一定會說——“我們需要線程互斥或同步”,沒錯,我們需要這個事情,于是我們的Singleton更新成1.1版,如下所示:
// version 1.1 public class Singleton { private static Singleton singleton = null ; private Singleton() { } public static Singleton getInstance() { if (singleton== null ) { synchronized (Singleton. class ) { singleton= new Singleton(); } } return singleton; } }
嗯,使用了Java的synchronized方法,看起來不錯哦。應該沒有問題了吧?!錯!這還是有問題!為什麼呢?前面已經說過,如果有多個線程同時通過(singleton== null)的條件檢查(因為他們并行運作),雖然我們的synchronized方法會幫助我們同步所有的線程,讓我們并行線程變成串行的一個一個去new,那不還是一樣的嗎?同樣會出現很多執行個體。嗯,确實如此!看來,還得把那個判斷(singleton== null)條件也同步起來。于是,我們的Singleton再次更新成1.2版本,如下所示:
// version 1.2 public class Singleton { private static Singleton singleton = null ; private Singleton() { } public static Singleton getInstance() { synchronized (Singleton. class ) { if (singleton== null ) { singleton= new Singleton(); } } return singleton; } } 不錯不錯,看似很不錯了。在多線程下應該沒有什麼問題了,不是嗎?的确是這樣的,1.2版的Singleton在多線程下的确沒有問題了,因為我們同步了所有的線程。隻不過嘛……,什麼?!還不行?!是的,還是有點小問題,我們本來隻是想讓new這個操作并行就可以了,現在,隻要是進入getInstance()的線程都得同步啊,注意,建立對象的動作隻有一次,後面的動作全是讀取那個成員變量,這些讀取的動作不需要線程同步啊。這樣的作法感覺非常極端啊,為了一個初始化的建立動作,居然讓我們達上了所有的讀操作,嚴重影響後續的性能啊! 還得改!嗯,看來,線上程同步前還得加一個(singleton== null)的條件判斷,如果對象已經建立了,那麼就不需要線程的同步了。OK,下面是1.3版的Singleton。
以下這個是最普遍的版本,比較繁瑣,可以通過反射來攻擊代碼,可以在在類中加一個計數器,建立第二個執行個體的時候抛出異常
// version 1.3 public class Singleton { private static volatile Singleton singleton = null ; private Singleton() { } //設定為私有,保證無法建立執行個體 public static Singleton getInstance() { if (singleton== null ) { //第一次判斷是否存在這個執行個體,存在就傳回 synchronized (Singleton. class ) { if (singleton== null ) { //線程同步的過程中,如果沒有就建立 singleton= new Singleton(); } } } return singleton; } }
這個反射攻擊的代碼 (唯一可以防止這種攻擊的就是枚舉類強化Singleton)
GatewayManager s1 = GatewayManager.getInstance(); Class c1 = Class.forName("com.iov.gatewayengine.framework.engine.GatewayManager"); Constructor[] cons = c1.getDeclaredConstructors(); Constructor cc1 = cons[0]; cc1.setAccessible(true); GatewayManager s2 = (GatewayManager) cc1.newInstance(null); System.out.println(s1 + "/" + s2); System.out.println(s1 == s2);
以上都是繁瑣的做法,屬于不優雅的代碼,但是通用型比較高
以下是EFFICTIVE JAVA 提供的三種方法,相比于上面的優雅且帥氣
public class Singleton{
public static final Singleton INTANCE = new Singleton();
private Singleton(){}
.......... 第一種: }
私有構造器隻被調用一次,用來執行個體化公有的靜态FINAL域,這種方法無法抵禦反射攻擊,
第二種: public class Singleton{
public static final Singleton INTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){ return INSTANCE; }
..........
}
以上兩種方法大緻相同,第二種的API更為靈活,可以随時改變該類是否為singleton的想法
但是以上兩種方法,在JAVA反序列化的時候則會建立一個新的執行個體,在我們的例子中,會導緻"假冒的Singleton",是以要在以上兩種方法中加上,一個readResolve的方法
public Object readResolve(){ return INSTANCE; }
最後一種終極大招,
public enum Singleton{ INSTANCE; ........... }
枚舉的方法 和公有域的方法類似,但是更加簡潔,提供了無償的序列化和反序列化的機制,絕對防止多種執行個體化,即時在面對反射攻擊和複雜的序列化問題的時候
關于DLC單例為何要加一個 volatile 關鍵字,下篇見