無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

唯一にならないシングルトン

昔はまった罠について。
複数のクラスローダーで同じクラスを読み込んだ場合、"別のクラス"になります。static変数も別物扱い。このため、シングルトンなクラスのインスタンス複数できたりします。

よくあるシングルトンクラス。privateなstatic変数に唯一のインスタンスを持ちます。

/**
 * 一般的なシングルトンクラス
 */
public class Singleton {

    // 唯一のインスタンス
    private static final Singleton INSTANCE = new Singleton();

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

このクラスをコンパイルして、"./bin"フォルダに配置します。
その上で以下のプログラムを実行。(注:プログラムの実行パスにSingletonクラスを通さないこと!)

public static final void main(String[] args)
throws Exception {

    // コンパイルしたSingletonクラスを"./bin"に配置し、
    // それをロードするクラスローダーを2つ作る。
    URL[] urls = new URL[] {
        new File( "./bin" ).toURI().toURL()
    };
    URLClassLoader a = URLClassLoader.newInstance( urls );
    URLClassLoader b = URLClassLoader.newInstance( urls );

    // 各クラスローダーから"Singleton"クラスをロード
    Class SingletonA = a.loadClass( "Singleton" );
    Class SingletonB = b.loadClass( "Singleton" );

    // getInstance()を呼び出しインスタンスを得る。
    Object instanceA = getSingleton( SingletonA );
    Object instanceB = getSingleton( SingletonB );

    // 同じインスタンスかチェック
    System.out.println( instanceA == instanceB ); // false // 別インスタンス!
    System.out.println( instanceA.getClass().getName() ); // Singleton / クラス名は同じ。
    System.out.println( instanceB.getClass().getName() ); // Singleton

    // そもそもクラスからして違う。
    System.out.println( SingletonA == SingletonB ); // false
}

/**
 * Singleton#getInstance()を呼び出して唯一のインスタンスを取得する。
 * @param singletonClass Singletonクラス
 * @return Singletonクラスの唯一のインスタンス
 */
static Object getSingleton( Class singletonClass )
throws Exception {
    Method m = singletonClass.getMethod( "getInstance", new Class[0] );
    return m.invoke( null, new Object[0] );
}

実行結果です。

false
Singleton
Singleton
false

というわけで、独自にクラスローダーを使っている場合は注意が必要です。特にプログラムの下位層でさりげなくクラスローダーが差し替えられていたりすると気づきにくい。実際、Tomcatで別コンテキストにあるサーブレット同士で同期制御するときに、synchronized用のオブジェクトをシングルトンで保持していたら、共有されてなかった orz →同時実行でエラー、とかあった気がします・・・。

なお、同じ原因で「同じクラスなのにキャストできない」という現象も起きます。こちらの方が遭遇率は高いかも。