使用ConcurrentHashMap缓存

时间:2014-11-13 17:43:11

标签: java multithreading collections concurrency hashmap

我有以下代码:

public class Cache {

    private final Map map = new ConcurrentHashMap();

    public Object get(Object key) {

        Object value = map.get(key);
        if (value == null) {
            value = new SomeObject();
            map.put(key, value);
        }

        return value;
    }
}

我的问题是: 映射的putget方法是线程安全的,但由于整个块未同步 - 多个线程可以两次添加相同的密钥吗?

4 个答案:

答案 0 :(得分:7)

putget是线程安全的,因为从不同的线程调用它们不会破坏数据结构(例如,可以使用普通的java.util.HashMap)。

但是,由于块未同步,您可能仍有多个线程添加相同的键: 两个线程都可以通过null检查,一个添加密钥并返回其值,然后第二个将使用新值覆盖该值并返回它。

答案 1 :(得分:2)

从Java 8开始,您还可以使用以下方法来防止添加重复的密钥:

public class Cache {

    private final Map map = new ConcurrentHashMap();

    public Object get(Object key) {

        Object value = map.computeIfAbsent(key, (key) -> {
          return new SomeObject();
        });

        return value;
    }
}

API docs状态:

  

如果指定的键尚未与值关联,则尝试   使用给定的映射函数计算其值并输入   进入此地图,除非为null。 整个方法调用已执行   从原子上讲,因此每个键最多只能应用一次该功能。一些   其他线程在此地图上尝试的更新操作可能是   在计算进行过程中被阻塞,因此计算应   简短而简单,不得尝试更新以下内容的任何其他映射   这张地图。

答案 2 :(得分:1)

  

多个线程可以两次添加相同的密钥吗?

是的,他们可以。要解决此问题,您可以:

1)使用putIfAbsent方法代替put。它非常快,但可以创建不必要的SomeObject实例。

2)使用双重检查锁定:

Object value = map.get(key);
if (value == null) {
    synchronized (map) {
        value = map.get(key);
        if (value == null) {
            value = new SomeObject();
            map.put(key, value);
        }
    }
}
return value;

锁定速度慢得多,但只会创建必要的对象

答案 3 :(得分:1)

您还可以将check和putIfAbsent结合使用,例如:

Object value = map.get(key);
if (value == null) {
    map.putIfAbsent(key, new SomeObject());
}
return value;

从而将不必要的新对象减少到在check和putIfAbsent之间的短时间内引入新条目的情况。

如果您感觉很幸运并且读取的数量大大超过地图写入数量,您还可以创建类似于CopyOnWriteArrayList的自己的写时复制地图。