Как убрать неатомарное использование check / put и сделать поток кода безопасным?

1

У меня есть код, из которого я пытаюсь получить экземпляр моего класса, поскольку я написал обертку вокруг java.util.logging.Logger.

Ниже приведен фрагмент кода в моем классе ClientLogger -

private static final Map<Class<?>, ClientLogger> s_classLoggers = new ConcurrentHashMap<Class<?>, ClientLogger>();

final private Logger m_logger;

private ClientLogger(final Class<?> caller) {
    m_logger = Logger.getInstance(caller);
}   

public static ClientLogger getInstance(final Class<?> klass) {
    final ClientLogger result;

    if (s_classLoggers.containsKey(klass)) {
        result = s_classLoggers.get(klass);
    } else {
        result = new ClientLogger(klass);
        s_classLoggers.put(klass, result);
    }

    return result;
}

И так я инициализирую его в своих других классах, где мне нужно использовать мой выше регистратор -

private static final ClientLogger s_logger = ClientLogger.getInstance(TestLogger.class);

Теперь, когда я запускаю свой инструмент статического анализа, он жалуется, как и в моем классе ClientLogger -

Non-atomic use of check/put on this line s_classLoggers.put(klass, result);

Поэтому я изменил свой код выше, чтобы сделать его потокобезопасным -

private static final ConcurrentHashMap<Class<?>, ClientLogger> s_classLoggers = new ConcurrentHashMap<Class<?>, ClientLogger>();

public static ClientLogger getInstance(final Class<?> klass) {
    ClientLogger result;

    result = s_classLoggers.putIfAbsent(klass, new ClientLogger(klass));
    // is below line thread safe and efficient?
    if (result == null) {
        result = new ClientLogger(klass);
    }       

    return result;
}

Ниже приводится способ инициализации моего экземпляра моего регистратора -

private static final ClientLogger s_logger = ClientLogger.getInstance(TestLogger.class);

Так безопасен ли мой код выше? Я делаю result == null check, поскольку в первый раз его не будет на карте, поэтому мне нужно сделать новое значение, и из-за этого мне нужно удалить окончательный модификатор результата.

  • 0
    Подумайте, эффективно ли это. Это создает ClientLogger для каждого вызова getInstance, даже если он отсутствует. Также подумайте, имеет ли это смысл. Как может случиться случай == null, и если это произойдет, почему он поможет инициализировать результат с локальной переменной, которая не хранится на карте?
  • 0
    @SimonFischer Я думаю, что вы правы, но для нулевого случая, это произойдет в первый раз при инициализации, когда я пытаюсь получить экземпляр logger, он приходит как ноль, так как putIfAbsent вернет ноль, если его там нет. Есть ли лучший способ сделать это?
Показать ещё 4 комментария
Теги:
multithreading
atomic
static-analysis

1 ответ

3
Лучший ответ

Что вам нужно - это Java 8

s_classLoggers.computeIfAbsent(klass, ClientLogger::new);

Это создаст объект, если это действительно необходимо.

Заметьте, что ClientLogger::new - потрясающая короткая рука, так как она короткая для k → new ClientLogger(k) что является коротким для

new Function<Class<?>, ClientLogger>() {
    public ClientLogger apply(Class<?> k) {
         return new ClientLogger(k);
    }
}

и lambda даже не генерирует класс во время компиляции, хотя JVM может (и делает это на Java 8) создавать класс во время выполнения.

В противном случае вы можете обнаружить, что блокировка записи более безопасна.

public static ClientLogger getInstance(final Class<?> klass) {
    ClientLogger result = s_classLoggers.get(klass);
    if (result != null) 
        return result; // fast path

    // slow, but rare path
    synchronized (s_classLoggers) {
        result = s_classLoggers.get(klass);
        if (result == null)
            s_classLoggers.put(klass, result = new ClientLogger(klass));
    }
    return result;
}
  • 0
    К сожалению, у меня нет Java 8 на данный момент. Есть ли другие способы, которыми мы можем сделать это в Java 7?
  • 0
    @ user2809564 Я обновил свой ответ. Замки не плохи, если они используются редко. Я предполагаю, что ваш кэш имеет достаточно высокую частоту попаданий, что он редко добавляет регистраторы, когда код нагревается.
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню