使此代码段线程安全的最佳方法是什么?
private static final Map<A, B> MAP = new HashMap<A, B>();
public static B putIfNeededAndGet(A key) {
B value = MAP.get(key);
if (value == null) {
value = buildB(...);
MAP.put(key, value);
}
return value;
}
private static B buildB(...) {
// business, can be quite long
}
以下是我能想到的几个解决方案:
ConcurrentHashMap
,但如果我很好理解,只是使原子put
和get
操作成为线程安全的,即它不确保仅为给定值调用buildB()
方法一次。Collections.synchronizedMap(new HashMap<A, B>())
,但我会遇到与第一点相同的问题。putIfNeededAndGet()
方法synchronized
,但我可以让很多线程一起访问这个方法,所以它可能非常昂贵。我可以提供哪些其他解决方案?
我知道这是网络上一个非常常见的话题,但我还没有找到一个清晰,完整且有效的例子。
答案 0 :(得分:5)
使用ConcurrentHashMap和您使用的延迟初始化模式
public static B putIfNeededAndGet(A key) {
B value = map.get(key);
if (value == null) {
value = buildB(...);
B oldValue = map.putIfAbsent(key, value);
if (oldValue != null) {
value = oldValue;
}
}
return value;
}
答案 1 :(得分:2)
这可能不是您正在寻找的答案,但使用Guava CacheBuilder
,它已经完成了所有这些以及更多:
private static final LoadingCache<A, B> CACHE = CacheBuilder.newBuilder()
.maximumSize(100) // if necessary
.build(
new CacheLoader<A, B>() {
public B load(A key) {
return buildB(key);
}
});
您还可以轻松添加定时到期和其他功能。
此缓存将确保load()
(或在您的情况下为buildB
)不会与同一key
同时调用。如果一个线程已经构建了B
,那么任何其他调用者都将等待该线程。
答案 2 :(得分:2)
在上面的解决方案中,许多线程可能会同时归类processB(...)
,因此所有线程都会计算出来。但在我的情况下,我使用的是Future
,而单个线程只将旧值作为null
获取,因此它只计算processB
其余值将等待f.get()
。
private static final ConcurrentMap<A, Future<B>> map = new ConcurrentHashMap<A, Future<B>>();
public static B putIfNeededAndGet(A key) {
while (true) {
Future<V> f = map.get(key);
if (f == null) {
Callable<B> eval = new Callable<V>() {
public B call() throws InterruptedException {
return buildB(...);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = map.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
ft.run();
}
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
}
}
}
答案 3 :(得分:1)
想想也许这对其他人也有用,使用java 8 lambdas我创建了这个对我有用的功能:
private <T> T getOrCreate(Object key, Map<Object, T> map,
Function<Object, T> creationFunction) {
T value = map.get(key);
// if the doesn't exist yet - create and add it
if (value == null) {
value = creationFunction.apply(key);
map.put(label, metric);
}
return value;
}
然后你可以像这样使用它:
Object o = getOrCreate(key, map, s -> createSpecialObjectWithKey(key));
我为特定的东西创建了这个,但是将上下文和代码更改为更一般的外观,这就是为什么我的creationFunction有一个参数,它也可以没有参数......
你也可以通过将Object更改为泛型类型来更多地通用它,如果不清楚,请告诉我,我将添加另一个示例。
更新:
我刚刚发现了Map.computeIfAbsent,基本上也是这样,得爱java 8 :)