Java 如何消除check/put的非原子用法并使代码线程安全?

Java 如何消除check/put的非原子用法并使代码线程安全?,java,multithreading,atomic,static-analysis,Java,Multithreading,Atomic,Static Analysis,我有一段代码,我试图从中获取类的实例,因为我编写了一个关于java.util.logging.Logger的包装器 下面是我的ClientLogger类中的代码片段- private static final Map<Class<?>, ClientLogger> s_classLoggers = new ConcurrentHashMap<Class<?>, ClientLogger>(); final private Logger m_log

我有一段代码,我试图从中获取类的实例,因为我编写了一个关于
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;
}
Non-atomic use of check/put on this line s_classLoggers.put(klass, result);
现在,当我运行静态分析工具时,它正在抱怨,就像我的
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;
}
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;
}

那么我上面的代码是线程安全的吗?我正在做
result==null
检查,因为这是第一次,它不会出现在映射中,所以我需要为它创建一个新的值,因此,我需要删除result的最后一个修饰符。

您需要的是Java 8

s_classLoggers.computeIfAbsent(klass, ClientLogger::new);
这将仅在确实需要时创建对象

注意,
ClientLogger::new
是惊人的速记,因为它是
k->newclientlogger(k)
的缩写,它是

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

新功能考虑一下它是否有效。这将为getInstance的每次调用创建一个ClientLogger,即使不存在。还要考虑一下它是否有意义。==null的情况怎么可能发生,如果发生了,为什么用一个未存储在映射中的局部变量初始化结果会有帮助?@SimonFischer我想你是对的,但是对于null的情况,当我试图获取logger的实例时,它将第一次发生初始化,由于putIfAbsent不存在时将返回null,因此它将作为null出现。有更好的方法吗?你是对的,我本来打算在收到你的回复的那一刻纠正我自己关于空的情况:-)但在这种情况下,为结果分配ClientLogger的新实例而不将其添加到映射中仍然是毫无意义的,因为在这种情况下,您返回的内容与地图中的内容不同。如果映射中没有以前的值,则要将新创建的实例指定给结果。但即使这样,如果存在现有的ClientLogger(),您也会希望避免创建新的ClientLogger()。相关:链接的解决方案很好,因为它在决定创建新实例之前就已经存在了。不幸的是,到目前为止,我还没有Java 8。在Java 7中还有其他方法可以做到吗?@user2809564我已经更新了我的答案。如果很少使用锁,锁也不错。我假设你的缓存有足够高的命中率,一旦代码变热,它很少添加记录器。是的,一旦代码变热,它应该是缓存,当我尝试你的建议时,它在
synchronized
上给了我错误?可能有什么遗漏了。@user2809564是的,在美国和Java,它是
同步的
而不是
同步的
,就像在英国英语中一样。是的,我自己能够纠正这个问题。最后一个问题,assylias给我指出了一个不使用synchronized的。你认为这对我来说也是另一种解决方案吗?
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;
}