2013/04/15

Cached Singleton by java.lang.ref.Reference

In Java, Using only-one instance, the following "Singleton" program is written, you know.
And this program uses a static inner class. This pattern is well known as "Lazy loading".
public class Singleton {
    private Singleton(){}

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

    private static class SingletonHolder {
        public static final Singleton INSTANCE = new Singleton();
    }
}
But the Singleton instance is not reclaimed without the ClassLoader is not disposed, because the ClassLoader holds the Shingleton class. So I will suggest how to create reclaimable Singleton class. The following program uses java.lang.ref.WeakReference.
public class Singleton {
    private Singleton() {}

    public static Singleton getInstance() {
        synchronized(SingletonHolder.class){
            Singleton referent = SingletonHolder.INSTANCE.get();
            if(referent == null){
                referent = new Singleton();
                SingletonHolder.INSTANCE = new WeakReference<>(referent);
            }
            return referent;
        }
    }

    private static class SingletonHolder {
        public static Reference<Singleton> INSTANCE = new WeakReference<>(new Singleton());
    }
}
If outer program does not hold a Strong Reference to Singleton instance, Singleton instance will be reclaimed whenever a garbage collection runs. A life of Singleton instance will be shorter and memory usage of the Java VM will be decreased.

Note that a return value of WeakReference.get() will be changed after a garbage collection. So the return value of WeakReferent.get() should be substituted for a local variable at line 6 of the above program. If the referent is null, a new Singleton instance is substituted for the same local variable at line 8. These two substitution is important, because of holding "Strong Reference". At last, getInstance() will return a valid Singleton instance certainly.

If you want a life of Singleton instance to be longer, change WeakReference class to SoftReference class at line 9 and 16.

Singletonデザインパターンとjava.lang.ref.Referenceによるキャッシング

皆さんもご存じのとおり、Javaで唯一のインスタンスを使いまわすときは、次のプログラム例のようにSingletonデザインパターンを使います。
またこのプログラムでは、Singletonオブジェクトの生成を遅らせるためにstaticのインナークラスと組み合わせて使っています。「Lazy loading」などと呼ばれている手法です。
/**
 *Lazy loadingを用いたSingletonクラスです。
 */
public class Singleton {
    private Singleton(){}

    /**
     *唯一のインスタンスを返します。
     *@return 唯一のインスタンス
     */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     *getInstance()を最初に呼び出したときに、SingletonHolderクラスがロードされます。
     *すると、INSTANCEフィールドにSingletonオブジェクトが代入されます。
     */
    private static class SingletonHolder {
        public static final Singleton INSTANCE = new Singleton();
    }
}
ところがSingletonデザインパターンには、Singletonのクラスを保持しているクラスローダが破棄されない限り、Singletonオブジェクトが生存し続ける問題点があります。そこで、java.lang.refパッケージのAPIと組み合わせる方法を提案します。次のプログラムを見てください。
/**
 *Lazy loadingとjava.lang.refパッケージを用いたSingletonクラスです。
 */
public class Singleton {
    private Singleton() {}

    /**
     *唯一のインスタンスを返します。
     *@return 唯一のインスタンス
     */
    public static Singleton getInstance() {
        synchronized(SingletonHolder.class){
            Singleton referent = SingletonHolder.INSTANCE.get();
            if(referent == null){
                referent = new Singleton();
                SingletonHolder.INSTANCE = new WeakReference<>(referent);
            }
            return referent;
        }
    }

    /**
     *getInstance()を最初に呼び出したときに、SingletonHolderクラスがロードされます。
     *すると、INSTANCEフィールドにSingletonオブジェクトが代入されます。
     */
    private static class SingletonHolder {
        public static Reference<singleton> INSTANCE = new WeakReference<>(new Singleton());
    }
}
WeakReferenceクラスを利用しています。外部プログラムがgetInstance()が返すSingletonオブジェクトを参照していなけなければ、マイナーGCが走るたびにSingletonインスタンスが破棄されるようになります。Singletonインスタンスの寿命が短くなり、Javaヒープの使用量を抑制できるでしょう。ところが、このようなプログラム例を記載した日本語記事が見当たりません。英語記事にはありますが、プログラム例がまちまちで怪しいプログラム例も散見されます。そこでどうしたら安全なSingletonになるかを考えてみたわけです。

WeakReference.get()はガーベジコレクション実行を挟んで、復帰値が変わる点に注意する必要があります。そこで上のプログラムの13行目では、WeakReference.get()の復帰値をreferent変数に代入させ、強参照を保持します。referentがnullだった場合、15行目で新しいリファレントをreferent変数に代入し、強参照を保持します。これによりgetInstance()を実行中にガーベジコレクタが起動しても、リファレントが破棄されるリスクを排除できるはずです。こうして18行目で有効なSingletonオブジェクトを確実に返せるでしょう。たとえば、15~16行目を1文にまとめて「SingletonHolder.INSTANCE = new WeakReference<>(new Singleton())」などとしていけないでしょう。

なお、Singletonインスタンスの寿命を延ばしたければ、16、27行目のWeakReferenceをSoftReferenceに変更すればよいです。SoftReferenceだとSingletonインスタンスがTenured世代領域までに昇進し、メモリ不足になるまで破棄されない点に注意します。