1. 程式人生 > >列舉實現單例原理:執行緒安全及發序列化依舊為單例原因

列舉實現單例原理:執行緒安全及發序列化依舊為單例原因

單例的列舉實現在《Effective Java》中有提到,因為其功能完整、使用簡潔、無償地提供了序列化機制、在面對複雜的序列化或者反射攻擊時仍然可以絕對防止多次例項化等優點,單元素的列舉型別被作者認為是實現Singleton的最佳方法。

其實現非常簡單,如下:

public enum Singleton {
INSTANCE;
private Singleton() {}
}

下面我們用一個列舉實現單個數據源例子來簡單驗證一下:
宣告一個列舉,用於獲取資料庫連線。

public enum DataSourceEnum {
DATASOURCE;
private DBConnection connection = null;
private DataSourceEnum() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}

模擬一個數據庫連線類:

public class DBConnection {}

測試通過列舉獲取的例項是否相同:

public class Main {
public static void main(String[] args) {
DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();
DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();
System.out.println(con1 == con2);
}
}

輸出結果為:true 結果表明兩次獲取返回了相同的例項。

下面深入瞭解一下為什麼列舉會滿足執行緒安全、序列化等標準。

在JDK5 中提供了大量的語法糖,列舉就是其中一種。
所謂 語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機學家 Peter.J.Landin 發明的一個術語,指在計算機語言中新增的某種語法,這種語法對語言的功能並沒有影響,但是但是更方便程式設計師使用。只是在編譯器上做了手腳,卻沒有提供對應的指令集來處理它。

就拿列舉來說,其實Enum就是一個普通的類,它繼承自java.lang.Enum類。

public enum DataSourceEnum {
DATASOURCE;
}

把上面列舉編譯後的位元組碼反編譯,得到的程式碼如下:

public final class DataSourceEnum extends Enum {
public static final DataSourceEnum DATASOURCE;
public static DataSourceEnum[] values();
public static DataSourceEnum valueOf(String s);
static {};
}

由反編譯後的程式碼可知,DATASOURCE 被宣告為 static 的,根據在【單例深思】餓漢式與類載入 中所描述的類載入過程,可以知道虛擬機器會保證一個類的() 方法在多執行緒環境中被正確的加鎖、同步。所以,列舉實現是在例項化時是執行緒安全。

接下來看看序列化問題:

Java規範中規定,每一個列舉型別極其定義的列舉變數在JVM中都是唯一的,因此在列舉型別的序列化和反序列化上,Java做了特殊的規定。
在序列化的時候Java僅僅是將列舉物件的name屬性輸出到結果中,反序列化的時候則是通過 java.lang.Enum 的 valueOf() 方法來根據名字查詢列舉物件。
也就是說,以下面列舉為例,序列化的時候只將 DATASOURCE 這個名稱輸出,反序列化的時候再通過這個名稱,查詢對於的列舉型別,因此反序列化後的例項也會和之前被序列化的物件例項相同。

public enum DataSourceEnum {
DATASOURCE;
}

由此可知,列舉天生保證序列化單例。