Лучшая идиома для создания отдельного синглтона для каждого аргумента типа универсального класса?
(Возможно, дополнительный вопрос к "Как создать универсальный синглтон-класс в Java?":)
class MyClass<T> {
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);
MyClass<?> instance = clz.newInstance();
s_instances.put(clz, instance);
return instance;
}
}
Есть ли лучшая идиома для наличия значения аргумента singleton-per-type-аргумент?
Изменить: Пожалуйста, не отвечайте просто, чтобы указать на отсутствие безопасности потока. Дело принято. Я спрашиваю, могу ли я сделать что-то более элегантное, чем эта карта.
2 ответа
Пожалуйста, не делай этого.
A. Ваш синглтон не безопасен для потоков.
Б. Помните о проблемах с двойной проверкой шаблонов в Java.
C. Действительно ли сложно иметь статический инициализатор в каждом классе и иметь:
static {
instance = new MySingleton();
}
а потом
public static MySingleton getInstance() {
return instance
}
И если вы действительно настаиваете -
1. Возможно, вы можете определить синглтон, который будет управлять типами в карте с экземпляром (ключ - это класс или полное имя класса, значение - это объект)
2. Вы можете добавить туда зарегистрированные желаемые типы (я предлагаю, чтобы они имели частные CTOR).
3. Используйте этот ответ, чтобы вызвать частный CTOR и создать экземпляр, который будет помещен в значение записи карты.
4. Укажите метод getInstance для репозитория, указанного в 1, с подписью:
public Object getInstanceByType(Class<?> clazz)
Этот метод получит экземпляр из внутренней карты.
Ваш метод не является потокобезопасным:
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;
}
Как только одна нить проходит строку, отмеченную //here1
второй поток может войти в метод до того, как первый поток окажется в строке, помеченной //here2
следовательно, создаем второй "синглтон" того же типа и перезаписываем первый на карте.
Быстрое решение было бы синхронизировать на карте:
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;
}
}
Однако это будет означать, что многим потокам придется ждать много времени, в конечном итоге, возможно, убив ваше приложение. Вероятно, вы должны сделать два шага:
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
вместо.