唯一にならないシングルトン
昔はまった罠について。
複数のクラスローダーで同じクラスを読み込んだ場合、"別のクラス"になります。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 →同時実行でエラー、とかあった気がします・・・。
なお、同じ原因で「同じクラスなのにキャストできない」という現象も起きます。こちらの方が遭遇率は高いかも。