Лучшая идиома для создания отдельного синглтона для каждого аргумента типа универсального класса?

(Возможно, дополнительный вопрос к "Как создать универсальный синглтон-класс в 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 вместо.

Другие вопросы по тегам