Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/303.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java concurrentHashMap用于检索对象或在不存在时创建对象的代码段(作为原子操作)_Java_Java.util.concurrent_Concurrent Programming - Fatal编程技术网

Java concurrentHashMap用于检索对象或在不存在时创建对象的代码段(作为原子操作)

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 但我如何使这种逻辑原子化呢 请不要给我像“同步”块这样的琐碎解决方案。 我希望这个问题能一劳永逸地解决。这问

在Java中,我想做如下操作:

   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)));