Java concurrentHashMap用于检索对象或在不存在时创建对象的代码段(作为原子操作)
在Java中,我想做如下操作:Java concurrentHashMap用于检索对象或在不存在时创建对象的代码段(作为原子操作),java,java.util.concurrent,concurrent-programming,Java,Java.util.concurrent,Concurrent Programming,在Java中,我想做如下操作: Object r = map.get(t); if (r == null) { r = create(); // creating r is an expensive operation. map.put(t, r); } 现在,代码片段可以在多线程环境中执行。 map可以是ConcurrentHashMap 但我如何使这种逻辑原子化呢 请不要给我像“同步”块这样的琐碎解决方案。 我希望这个问题能一劳永逸地解决。这问
Object r = map.get(t);
if (r == null) {
r = create(); // creating r is an expensive operation.
map.put(t, r);
}
现在,代码片段可以在多线程环境中执行。
map
可以是ConcurrentHashMap
但我如何使这种逻辑原子化呢
请不要给我像“同步”块这样的琐碎解决方案。
我希望这个问题能一劳永逸地解决。这问题已经由我巧妙地解决了 使用和调用。这将返回一个对象。如果确实需要
Map
实现,可以调用
也有使用its的旧版本,但不推荐使用CacheBuilder
方法
当然,您也可以手动实现它,但是正确地实现它是非常重要的。需要考虑的几个方面是:
- 您希望避免使用相同的输入调用两次
create
- 您希望等待当前线程完成创建,但不希望使用空闲循环完成创建
- 您希望避免在良好的情况下进行同步(即元素已经在映射中)
- 如果两个
调用同时发生,您希望每个调用方只等待与他相关的一个调用create
- 我知道这可能不是您想要的,但为了便于讨论,我会将其包括在内
public Object ensureExistsInMap(Map map, Object t) {
Object r = map.get(t);
if (r != null) return r; // we know for sure it exists
synchronized (creationLock) {
// multiple threads might have come this far if r was null
// outside the synchronized block
r = map.get(t);
if (r != null) return r;
r = create();
map.put(t, r);
return r;
}
}
您所描述的基本上是延迟初始化 下面是一个使用双锁和现代Java锁的示例
private static Map<Object, Object> instances = new ConcurrentHashMap<Object, Object>();
private static Lock createLock = new ReentrantLock();
private Multitone() {}
public static Object getInstance(Object key) {
Object instance = instances.get(key);
if (instance == null) {
createLock.lock();
try {
if (instance == null) {
instance = createInstance();
instances.put(key, instance);
}
} finally {
createLock.unlock();
}
}
return instance;
}
私有静态映射实例=新的ConcurrentHashMap();
私有静态锁createLock=new ReentrantLock();
专用多音(){}
公共静态对象getInstance(对象键){
Object instance=instances.get(key);
if(实例==null){
createLock.lock();
试一试{
if(实例==null){
instance=createInstance();
实例。放置(键,实例);
}
}最后{
createLock.unlock();
}
}
返回实例;
}
我认为解决方案在实践中是以并发方式记录的。
诀窍是在地图中使用未来而不是R作为对象
虽然我不喜欢这个答案,因为它看起来太复杂了
代码如下:
public class Memorizer<A, V> implements Computable<A, V> {
private final ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memorizer(Computable<A, V> c) { this.c = c; }
public V compute(final A arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) { f = ft; ft.run(); }
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}
公共类存储器实现可计算{
私有最终ConcurrentMap缓存=新ConcurrentHashMap();
私有最终可计算c;
公共存储器(可计算c){this.c=c;}
public V compute(最后一个参数)抛出InterruptedException{
while(true){
Future f=cache.get(arg);
如果(f==null){
Callable eval=新的Callable(){
public V call()抛出InterruptedException{
返回c.compute(arg);
}
};
FutureTask ft=新的FutureTask(eval);
f=缓存.putIfAbsent(arg,ft);
如果(f==null){f=ft;ft.run();}
试一试{
返回f.get();
}捕获(取消异常e){
cache.remove(arg,f);
}捕获(执行例外){
扔掉可清洗的垃圾(如getCause());
}
}
}
试试看
自Java 8以来,您一直在寻找方法
ConcurrentMap.ComputeFabSent
:
相当于此映射的以下步骤,但原子:
V oldValue = map.get(key);
if (oldValue == null) {
V newValue = mappingFunction.apply(key);
if (newValue != null) {
return map.putIfAbsent(key, newValue);
} else {
return null;
}
} else {
return oldValue;
}
最常见的用法是构造一个新对象作为初始映射值或记忆结果,我认为这就是您要寻找的,如:
Value v = map.computeIfAbsent(key, k -> new Value(f(k)));
没有同步是无法做到的。如果你要使用多线程,你最好还是习惯使用同步的方式。@claesv:但是有一些方法可以编写它,而不需要在每次访问时都进行同步。@Joachimsuer好的,是的,也许我误解了这个问题。@claesv我对“同步”没问题,我只是想知道什么最好的方法是,对于这个案例,makeComputingMap是deprecated@AviramSegal:啊!我不知道,谢谢!我更新了我的答案。有一个缺点:没有两个
create
调用会同时执行,即使是对于不同的键。你完全正确。这是多音模式。但是你能提供一段java代码来解决它吗?为什么不使用并发hashmap?我很惊讶它可能已经过时了?你是对的,这是一个问题,我修复了他们的示例。他们的示例可能会失败(hashmap不允许同时获取和放置)感谢您的帮助,但我认为它仍然有缺陷。您正在ConcurrentHashMap上进行同步,因此假设它内部使用的锁与我认为不是这种情况的锁相同(或者如果是,它将通过accindent工作)一般来说,您是正确的,但我知道ConcurrentHashMap的内部结构是如何工作的,它是正常的。除了复杂之外,它只对您隐藏同步(它发生在putIfAbsent中)而且不阻止它,它也可能会工作得慢一点,需要更多的时间memory@AviramSegal:如果需要避免双重初始化,您将需要在某个时间点进行一些同步。但您可以将其移出公共路径并减少其影响。这是正确的,但不会节省太多,因为该路径中的子贴图数量有限并发哈希映射。但我不确定它在实践中会节省多少。这不起作用,因为两个线程可以同时进入if语句,对吗?这很有效,因为putIfAbsent以原子方式执行,V)我喜欢这种没有额外依赖性的方法。ConcurrentHashMap
是一个很好的使用实现。
Value v = map.computeIfAbsent(key, k -> new Value(f(k)));