我有以下代码:
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;
}
}
我的问题是:
映射的put
和get
方法是线程安全的,但由于整个块未同步 - 多个线程可以两次添加相同的密钥吗?
答案 0 :(得分:7)
put
和get
是线程安全的,因为从不同的线程调用它们不会破坏数据结构(例如,可以使用普通的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的自己的写时复制地图。