代码之家  ›  专栏  ›  技术社区  ›  Chris Dail

在使用ConcurrentMap的putIfAbsent之前,是否应该检查映射是否包含密钥

  •  69
  • Chris Dail  · 技术社区  · 14 年前

    我一直在使用Java的ConcurrentMap来实现一个可以从多个线程使用的映射。putIfAbsent是一种很好的方法,它比使用标准的map操作更容易读/写。我有一些代码如下所示:

    ConcurrentMap<String, Set<X>> map = new ConcurrentHashMap<String, Set<X>>();
    
    // ...
    
    map.putIfAbsent(name, new HashSet<X>());
    map.get(name).add(Y);
    

    if (!map.containsKey(name)) {
        map.putIfAbsent(name, new HashSet<X>());
    }
    map.get(name).add(Y);
    

    有了这个更改,它就失去了一点可读性,但不需要每次都创建HashSet。在这种情况下哪个更好?我倾向于支持第一个,因为它更具可读性。第二种可能表现更好,可能更正确。也许有比这两个更好的方法。

    以这种方式使用putIfAbsent的最佳实践是什么?

    6 回复  |  直到 8 年前
        1
  •  107
  •   Tom Hawtin - tackline    9 年前

    并发性很难。如果您要麻烦使用并发映射而不是直接锁定,那么不妨尝试一下。确实,不要做过多的查找。

    Set<X> set = map.get(name);
    if (set == null) {
        final Set<X> value = new HashSet<X>();
        set = map.putIfAbsent(name, value);
        if (set == null) {
            set = value;
        }
    }
    

    (免责声明:从我的头上。未测试。未编译。等等)

    更新: computeIfAbsent 默认方法为 ConcurrentMap (和 Map 这很有趣,因为这种实现对于 ). (1.7增加了“菱形运算符” <> .)

    Set<X> set = map.computeIfAbsent(name, n -> new HashSet<>());
    

    HashSet .)

        2
  •  16
  •   Jed Wesley-Smith    12 年前

    在Guava11中,MapMaker被弃用,取而代之的是Cache/LocalCache/CacheBuilder。这在用法上有点复杂,但基本上是同构的。

        3
  •  5
  •   Donald Raab    7 年前

    MutableMap.getIfAbsentPut(K, Function0<? extends V>) Eclipse Collections GS Collections ).

    与打电话相比的优势 get() ,执行空检查,然后调用 putIfAbsent() org.eclipse.collections.impl.map.mutable.ConcurrentHashMap ,实施 getIfAbsentPut() 也是线程安全和原子的。

    import org.eclipse.collections.impl.map.mutable.ConcurrentHashMap;
    ...
    ConcurrentHashMap<String, MyObject> map = new ConcurrentHashMap<>();
    map.getIfAbsentPut("key", () -> someExpensiveComputation());
    

    实施 org.eclipse.collections网站.impl.map.mutable变量.ConcurrentHashMap 是真正的非阻塞。尽管我们尽了最大努力避免不必要地调用工厂函数,但在争用期间仍有可能多次调用工厂函数。

    ConcurrentHashMap.computeIfAbsent(K, Function<? super K,? extends V>) . 此方法的Javadoc声明:

    整个方法调用是以原子方式执行的,因此函数 每个键最多应用一次。某些尝试的更新操作 在执行计算时,其他线程可能会阻止此映射 所以计算应该简短。。。

    注意:我是Eclipse集合的提交者。

        4
  •  3
  •   karmakaze    11 年前

    Set<X> initial = new HashSet<X>();
    ...
    Set<X> set = map.putIfAbsent(name, initial);
    if (set == null) {
        set = initial;
        initial = new HashSet<X>();
    }
    set.add(Y);
    

    我最近使用原子整数映射值,而不是设置。

        5
  •  2
  •   Nathan    7 年前

    在5年多的时间里,我不敢相信没有人提到或发布过使用 解决这个问题;以及本页的几种解决方案

    对于这个特定的问题,使用ThreadLocals并不是唯一考虑的问题 最佳实践 在期间

    例如:

    private final ThreadLocal<HashSet<X>> 
      threadCache = new ThreadLocal<HashSet<X>>() {
          @Override
          protected
          HashSet<X> initialValue() {
              return new HashSet<X>();
          }
      };
    
    
    private final ConcurrentMap<String, Set<X>> 
      map = new ConcurrentHashMap<String, Set<X>>();
    

    // minimize object creation during thread contention
    final Set<X> cached = threadCache.get();
    
    Set<X> data = map.putIfAbsent("foo", cached);
    if (data == null) {
        // reset the cached value in the ThreadLocal
        listCache.set(new HashSet<X>());
        data = cached;
    }
    
    // make sure that the access to the set is thread safe
    synchronized(data) {
        data.add(object);
    }
    
        6
  •  0
  •   ggrandes    10 年前

    我的通用近似值:

    public class ConcurrentHashMapWithInit<K, V> extends ConcurrentHashMap<K, V> {
      private static final long serialVersionUID = 42L;
    
      public V initIfAbsent(final K key) {
        V value = get(key);
        if (value == null) {
          value = initialValue();
          final V x = putIfAbsent(key, value);
          value = (x != null) ? x : value;
        }
        return value;
      }
    
      protected V initialValue() {
        return null;
      }
    }
    

    public static void main(final String[] args) throws Throwable {
      ConcurrentHashMapWithInit<String, HashSet<String>> map = 
            new ConcurrentHashMapWithInit<String, HashSet<String>>() {
        private static final long serialVersionUID = 42L;
    
        @Override
        protected HashSet<String> initialValue() {
          return new HashSet<String>();
        }
      };
      map.initIfAbsent("s1").add("chao");
      map.initIfAbsent("s2").add("bye");
      System.out.println(map.toString());
    }