这是一个我也在寻找答案的问题。该方法putIfAbsent
实际上并没有解决额外的对象创建问题,它只是确保这些对象中的一个不会替换另一个。但是线程之间的竞争条件会导致多个对象实例化。我可以为这个问题找到 3 个解决方案(并且我会遵循这个优先顺序):
1-如果您使用的是 Java 8,实现这一目标的最佳方法可能computeIfAbsent
是ConcurrentMap
. 您只需要给它一个将同步执行的计算函数(至少对于ConcurrentHashMap
实现而言)。例子:
private final ConcurrentMap<String, List<String>> entries =
new ConcurrentHashMap<String, List<String>>();
public void method1(String key, String value) {
entries.computeIfAbsent(key, s -> new ArrayList<String>())
.add(value);
}
这是来自的javadoc ConcurrentHashMap.computeIfAbsent
:
如果指定的键尚未与值关联,则尝试使用给定的映射函数计算其值并将其输入到此映射中,除非为空。整个方法调用以原子方式执行,因此每个键最多应用一次该函数。在计算过程中,其他线程对该映射的某些尝试更新操作可能会被阻塞,因此计算应该简短而简单,并且不得尝试更新该映射的任何其他映射。
2- 如果你不能使用 Java 8,你可以使用Guava
's LoadingCache
,这是线程安全的。你给它定义了一个加载函数(就像compute
上面的函数一样),你可以确定它会被同步调用。例子:
private final LoadingCache<String, List<String>> entries = CacheBuilder.newBuilder()
.build(new CacheLoader<String, List<String>>() {
@Override
public List<String> load(String s) throws Exception {
return new ArrayList<String>();
}
});
public void method2(String key, String value) {
entries.getUnchecked(key).add(value);
}
3-如果您也不能使用 Guava,您可以随时手动同步并进行双重检查锁定。例子:
private final ConcurrentMap<String, List<String>> entries =
new ConcurrentHashMap<String, List<String>>();
public void method3(String key, String value) {
List<String> existing = entries.get(key);
if (existing != null) {
existing.add(value);
} else {
synchronized (entries) {
List<String> existingSynchronized = entries.get(key);
if (existingSynchronized != null) {
existingSynchronized.add(value);
} else {
List<String> newList = new ArrayList<>();
newList.add(value);
entries.put(key, newList);
}
}
}
}
我对所有这 3 个方法以及非同步方法做了一个示例实现,这会导致额外的对象创建: http: //pastebin.com/qZ4DUjTr