0

我们使用 HashMap 来缓存方法上的注释查找。使用 Spring 的AnnotationUtils.findAnnotation检索注释。不使用缓存会导致性能严重下降。

我们的实现看起来像:

public class SomeService {

    // Caches annotations on methods. The value can be null!
    private static final Map<Method, MyAnnotation> ANNOTATION_CACHE = new HashMap<Method, MyAnnotation>();

    private MyAnnotation findAnnotation(Method m) {
        if (ANNOTATION_CACHE.containsKey(m)) {
            return ANNOTATION_CACHE.get(m);
        }

        MyAnnotation a = AnnotationUtils.findAnnotation(m, MyAnnotation.class);
        ANNOTATION_CACHE.put(m, a);

        return a;
    }

    public void doSomethingWith(Class<?> clazz) {
        for (Method m : clazz.getMethods()) {
            MyAnnotation a = findAnnotation(m);
            if (a != null) {
                // do something with annotation a
            }
        }
    }
}

现在的问题是我是否需要同步对 ANNOTATION_CACHE Map 的访问。可能发生的最糟糕的事情是两个并行线程将相同的 (m, a) 对放入缓存映射中,这不会造成伤害,不是吗?

我的第一个想法是使用 ConcurrentHashMap,但它不允许空值(如果方法没有注释 => null,则此处需要)。使用 Collections.synchronizedMap() 并同步对地图的每次访问也不理想,因为这个 doSomethingWith() 方法被非常频繁地调用。

那么这种情况下是否真的需要同步对HashMap的访问呢?缓存在运行时从不更改,键/值对只插入一次,永远不会被删除,但会被读取多次。

有什么想法吗?

4

2 回答 2

3

如果您有一个专门写入地图的阶段,然后有一个专门读取地图的不同阶段,那么您不需要并发收集。您还可以通过使用不可变的映射写后阶段包装它来确保映射保持不变。

例如,使用 Guava 的不可变映射

ImmutableMap.copyOf(map);


如果您预见到对集合的并发读/写/删除访问,那么您绝对应该使用 ConcurrentHashMap;因为读/写/删除操作不是原子的,你最终可能会得到一些非常奇怪的结果。


我的第一个想法是使用 ConcurrentHashMap,但它不允许空值(如果方法没有注释 => null,则此处需要)

然后不要一开始就插入空值;更好的是,从地图中删除现有的密钥。

于 2013-03-19T15:04:32.013 回答
0

现在的问题是我是否需要同步对 ANNOTATION_CACHE Map 的访问。可能发生的最糟糕的事情是两个并行线程将相同的 (m, a) 对放入缓存映射中,这不会造成伤害,不是吗?

是的,如果第一个 put 需要扩展支持 hashmap 的数组,这可能会造成伤害

如果在第一个 put 扩展 hashmap 时调用了第二个 put,则可能会发生不良和不可预测的结果。

于 2014-06-28T14:22:52.303 回答