1. 程式人生 > >Initialization On Demand Holder idiom的實現探討

Initialization On Demand Holder idiom的實現探討

SingletonLogoTransparent

起源

程曉明同學的文章“雙重檢查鎖定與延遲初始化”中,提到了對於單例模式的“Initialization On Demand Holder idiom”實現方案。

這個方案的技術實質是利用Java類初始化的LC鎖。相比其他實現方案(如double-checked locking等),該技術方案的實現程式碼較為簡潔,並且在所有版本的編譯器中都是可行的。該方案程式碼如下:

public class InstanceFactory {
    private static class InstanceHolder {
        public static Instance instance = new Instance();
    }

    public static Instance getInstance() {
        return InstanceHolder.instance ;  //這裡將導致InstanceHolder類被初始化
    }
}

根據wikipedia中的解釋,“雙重檢查鎖定與延遲初始化”這篇文章中的實現,實際上是Steve Quirk早期的實現,Bill Pugh在其基礎上將LazyHolder.INSTANCE的修飾符修改為private static final,實現程式碼如下:

public class Something {
	private Something() {}

	private static class LazyHolder {
		private static final Something INSTANCE = new Something();
	}

	public static Something getInstance() {
		return LazyHolder.INSTANCE;
	}
}

然而,在另外一篇wikipedia的說明中,LazyHolder.INSTANCE的修飾符則為public static final

public class Singleton {
	// Private constructor prevents instantiation from other classes
	private Singleton() { }

	/**
	* SingletonHolder is loaded on the first execution of Singleton.getInstance()
	* or the first access to SingletonHolder.INSTANCE, not before.
	*/
	private static class SingletonHolder {
		public static final Singleton INSTANCE = new Singleton();
	}

	public static Singleton getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

那麼上述三種實現,究竟哪一種實現較為合理?

在討論這個問題前,首先我們需要明確的是,上述三種方案都是可以保證執行緒安全的。
JLS12.4中關於一個類或介面將被立即初始化的相關說明:

  1. T is a class and an instance of T is created.
  2. T is a class and a static method declared by T is invoked.
  3. A static field declared by T is assigned.
  4. A static field declared by T is used and the field is not a constant variable (§4.12.4).
  5. T is a top level class (§7.6), and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.

顯然,上述任意一種實現首次執行Singleton.getInstance()時,由於使用InstanceHolder.instance變數,將會導致InstanceHolder初始化,符合第四條。注意final關鍵字修飾的變數,並不一定是constant variable。引用JLS4.12.4中的相關說明:

A variable of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28), is called a constant variable.

只有基本型別或者是String型別的變數,才可能成為constant variable。
回到剛才的問題,在stackoverflow上有相關問題的回答。

  1. 然後是關於INSTANCE的訪問許可權問題:
    http://stackoverflow.com/questions/21604243/correct-implementation-of-initialization-on-demand-holder-idiom
    這裡提出的觀點是建議如下形式的實現:
    public class Singleton {
        ...
        private static class LazyHolder {
            static final Singleton INSTANCE = new Singleton();
        }
        public static Singleton getInstance() {
            return LazyHolder.INSTANCE;
        }
    }
    

    即並不在INSTANCE變數前新增public或者private修飾,即使用預設的package private訪問許可權。
    不用public的原因是這個修飾不夠合理,作者認為事實上在LazyHolder是被限定為private的情況下,使用public來修飾INSTANCE並沒有問題,然而如果有人修改LazyHolder為public,那麼就可能出現問題。
    不用private的原因是,Java編譯器中會保證類內部的private欄位無法被外部訪問,然而JLS6.6.1中有相應的規定:

Otherwise, if the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

所以外部類擁有對內部類中private欄位的訪問許可權,那麼在這個情況下,編譯器就會有一些小技巧來保證外部類對內部類private欄位的訪問許可權,即在內部類中插入packge private method,使得外部類呼叫這些getter和setter方法的形式來訪問內部類的private欄位。

綜上所述,從執行緒安全的角度而言,上述的幾種“Initialization On Demand Holder idiom”實現都是沒有問題的。如果討論細節上的合理性,那麼更推薦最後一種實現,即:

public class Singleton {
    ...
    private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}