閱讀440 返回首頁    go 魔獸


單例模式補充(四)

Inspired by Effective Java.

Singleton模式是在編程實踐中應用最廣泛的幾種設計模式之一。以前知道的,實現單例的方法有兩種(下麵的A、B)。剛剛在讀《Effective Java的時候》學到一種新的更好的方法(E):單元素的枚舉類型。同時通過網上資料也知道了其他兩種方法(C、D)。最後一種在Java中從1.5版本開始支持,其他語言在驗證後說明。

A.餓漢式(類加載的時候就創建實例)。
代碼如下:

public class MaYun {
public static final Mayun instance = new Mayun(); //靜態的final的MaYun
private MaYun() {
//MaYun誕生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo綠眉綠眼的望著。。。”);
}
}
Call:MaYun.instance.splitAlipay();

Feature:可以通過反射機製攻擊;線程安全[多個類加載器除外]。

A+.餓漢變種[推薦]

public class MaYun {
private static Mayun instance = new Mayun();
private static getInstance() {
return instance;
}
private MaYun() {
//MaYun誕生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo綠眉綠眼的望著。。。”);
}
}

A++.餓漢變種(類初始化的時候實例化instance):

public class MaYun {
private MaYun instance = null;
static {
instance = new MaYun();
}
private MaYun() {
//MaYun誕生要做的事情
}
public static MaYun getInstance() {
return this.instance;
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo綠眉綠眼的望著。。。”);
}
}

B.懶漢式。
代碼如下:

public class MaYun {
private static MaYun instance = null;
private MaYun() {
//MaYun誕生要做的事情
}
public static MaYun getInstance() {
if (instance == null) {
instance = new MaYun();
}
return instance;
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo綠眉綠眼的望著。。。”);
}
}
Call:MaYun.getInstance().splitAlipay();

Feature:延時加載;線程不安全,多線程下不能正常工作;需要額外的工作(Serializable、transient、readResolve())來實現序列化。

B+.懶漢式變種。

public class MaYun {
private static MaYun instance = null;
private MaYun() {
//MaYun誕生要做的事情
}
public static synchronized MaYun getInstance() {
if (instance == null) {
instance = new MaYun();
}
return instance;
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo綠眉綠眼的望著。。。”);
}
}

Feature:線程安全;效率比較低,因為需要線程同步的時候比較少。

C.靜態內部類[推薦]。
代碼如下:

public class MaYun {
private static class SigletonHolder {
private static final instance = new MaYun();
}
public static final getInstance() {
return SigletonHolder.instance;
}
private MaYun() {
//MaYun誕生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo綠眉綠眼的望著。。。”);
}
Call:MaYun.getInstance().splitAlipay();

Feature:線程安全;延遲加載。

D.雙重校驗鎖[不推薦]。
代碼如下:

public class MaYun {
private volatile static MaYun instance;
private MaYun (){}
public static MaYun getInstance() {
if (instance == null) {
synchronized (MaYun.class) {
if (instance == null) {
instance = new MaYun();
}
}
}
return instance;
}
}

Feature:jdk1.5之後才能正常達到單例效果。

E.編寫一個包含單個元素的枚舉類型[極推薦]。
代碼如下:

public enum MaYun {
himself; //定義一個枚舉的元素,就代表MaYun的一個實例
private String anotherField;
MaYun() {
//MaYun誕生要做的事情
//這個方法也可以去掉。將構造時候需要做的事情放在instance賦值的時候:
/** himself = MaYun() {
* //MaYun誕生要做的事情
* }
**/
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo綠眉綠眼的望著。。。”);
}
}
Call:MaYun.himself.splitAlipay();

Feature:從Java1.5開始支持;無償提供序列化機製,絕對防止多次實例化,即使在麵對複雜的序列化或者反射攻擊的時候。

總之,五類:懶漢,惡漢,雙重校驗鎖,靜態內部類,枚舉。
惡漢:因為加載類的時候就創建實例,所以線程安全(多個ClassLoader存在時例外)。缺點是不能延時加載。
懶漢:需要加鎖才能實現多線程同步,但是效率會降低。優點是延時加載。
雙重校驗鎖:麻煩,在當前Java內存模型中不一定都管用,某些平台和編譯器甚至是錯誤的,因為instance = new MaYun()這種代碼在不同編譯器上的行為和實現方式不可預知。
靜態內部類:延遲加載,減少內存開銷。因為用到的時候才加載,避免了靜態field在單例類加載時即進入到堆內存的permanent代而永遠得不到回收的缺點(大多數垃圾回收算法是這樣)。
枚舉:很好,不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。但是失去了類的一些特性,沒有延遲加載,用的人也太少了~~


最後更新:2017-04-03 12:55:13

  上一篇:go 2012年藍橋杯【初賽試題】 轉方陣
  下一篇:go 怎樣在vs2010中添加圖片資源呢?