2013/04/15

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世代領域までに昇進し、メモリ不足になるまで破棄されない点に注意します。

0 件のコメント:

コメントを投稿