あなたのメソッドはスレッドセーフではありません:
private static Map<Class<MyClass<?>>, MyClass<?>> s_instances =
new HashMap<Class<MyClass<?>>, MyClass<?>>();
public static MyClass<?> blah(Class<MyClass<?>> clz)
throws InstantiationException, IllegalAccessException {
if (s_instances.get(clz) != null)
return s_instances.get(clz);
// here1
MyClass<?> instance = clz.newInstance();
s_instances.put(clz, instance);
// here2
return instance;
}
1つのスレッドがマークされた行を通過すると//here1
、最初のスレッドがマークされた行に到達する前に2番目のスレッドがメソッドに入る可能性があります//here2
。したがって、同じ種類の2番目の「シングルトン」が作成され、マップの最初のスレッドが上書きされます。
手っ取り早い解決策は、マップ上で同期することです。
public static MyClass<?> blah(Class<MyClass<?>> clz)
throws InstantiationException, IllegalAccessException {
synchronized(s_instances){
if (s_instances.get(clz) != null)
return s_instances.get(clz);
// here1
MyClass<?> instance = clz.newInstance();
s_instances.put(clz, instance);
// here2
return instance;
}
}
ただし、これは、多くのスレッドが多くの時間を待たなければならず、最終的にはアプリケーションを強制終了することを意味します。おそらくあなたがすべきことは2段階の解決策です:
public static MyClass<?> blah(Class<MyClass<?>> clz)
throws InstantiationException, IllegalAccessException {
Object candidate = s_instances.get(clz);
if(clz.isInstance(candidate)){ // implicit null check
return clz.cast(candidate);
}
synchronized(s_instances){
Object candidate = s_instances.get(clz);
if(clz.isInstance(candidate)){ // gotta check a second time in a
return clz.cast(candidate); // synchronized context
}
MyClass<?> instance = clz.newInstance();
s_instances.put(clz, instance);
return instance;
}
}
また、HashMapは同時アクセスには適していないため、次のようにラップする必要がありますCollections.synchronizedMap()
。
private static Map<Class<MyClass<?>>, MyClass<?>> s_instances =
Collections.synchronizedMap(new HashMap<Class<MyClass<?>>, MyClass<?>>());
またはConcurrentHashMap
代わりに行きます。