代码之家  ›  专栏  ›  技术社区  ›  Ryan Ahearn

Java同步块vs.Collections.synchronizedMap

  •  81
  • Ryan Ahearn  · 技术社区  · 15 年前

    是否设置了以下代码以正确同步上的调用 synchronizedMap

    public class MyClass {
      private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());
    
      public void doWork(String key) {
        List<String> values = null;
        while ((values = synchronizedMap.remove(key)) != null) {
          //do something with values
        }
      }
    
      public static void addToMap(String key, String value) {
        synchronized (synchronizedMap) {
          if (synchronizedMap.containsKey(key)) {
            synchronizedMap.get(key).add(value);
          }
          else {
            List<String> valuesList = new ArrayList<String>();
            valuesList.add(value);
            synchronizedMap.put(key, valuesList);
          }
        }
      }
    }
    

    据我所知,我需要同步块 addToMap() remove() containsKey() put() 但我不需要在中使用同步块 doWork() addToMap() 之前 删除() 返回,因为我最初使用 Collections.synchronizedMap() . 对吗?有更好的方法吗?

    7 回复  |  直到 15 年前
        1
  •  90
  •   Yuval Adam    15 年前

    Collections.synchronizedMap() 保证要在映射上运行的每个原子操作都将同步。

    但是,在地图上运行两个(或更多)操作必须在一个块中同步。

        2
  •  15
  •   TofuBeer    15 年前
        3
  •  13
  •   JLR    15 年前

    潜在的 查找代码中的细微错误。

    更新: 因为他使用的是map.remove(),所以这个描述并不完全正确。我第一次错过了这个事实(感谢问题的作者指出这一点。我将其余部分保持原样,但将主要陈述改为有。) 虫子。]

    嫁妆 您可以通过线程安全的方式从映射中获取列表值。但是,之后,您将在不安全的情况下访问该列表。例如,一个线程可能正在使用中的列表 synchronizedMap.get(键).add(值) . 这两个访问是不同步的。经验法则是集合的线程安全保证不会扩展到它们存储的键或值。

    您可以通过在映射中插入一个同步列表来修复此问题,如

    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list
    

    嫁妆 :

      public void doWork(String key) {
        List<String> values = null;
        while ((values = synchronizedMap.remove(key)) != null) {
          synchronized (synchronizedMap) {
              //do something with values
          }
        }
      }
    

    最后一个选项将稍微限制并发性,但在IMO中更加清晰。

    该类在依赖于其线程安全性的程序中可与哈希表完全互操作 但不是关于它的同步细节 .

    最后一件事。:)这句伟大的引用自 Java并发编程实战 总是帮助我设计调试多线程程序。

        4
  •  11
  •   Sergey ChrisH    9 年前

    是的,您正在正确同步。我将更详细地解释这一点。 只有在必须依赖synchronizedMap对象的方法调用序列中的后续方法调用中先前方法调用的结果的情况下,才能同步synchronizedMap对象上的两个或多个方法调用。

    synchronized (synchronizedMap) {
        if (synchronizedMap.containsKey(key)) {
            synchronizedMap.get(key).add(value);
        }
        else {
            List<String> valuesList = new ArrayList<String>();
            valuesList.add(value);
            synchronizedMap.put(key, valuesList);
        }
    }
    

    在此代码中

    synchronizedMap.get(key).add(value);
    

    synchronizedMap.put(key, valuesList);
    

    方法调用依赖于上一次调用的结果

    synchronizedMap.containsKey(key)
    

    方法调用。

    例如 thread 1 正在执行该方法 addToMap() thread 2 正在执行该方法 doWork() 上的方法调用序列 synchronizedMap 对象可能如下所示: Thread 1

    同步映射容器(键)
    

    结果是“ true 在操作系统将执行控制切换到 线程2 它已经执行了

    synchronizedMap.remove(key)
    

    线程1

    相信 同步地图 对象包含 key NullPointerException 将被抛出,因为 synchronizedMap.get(key) 会回来的 null 如果在 同步地图 对象不依赖于彼此的结果,因此不需要同步序列。

    synchronizedMap.put(key1, valuesList1);
    synchronizedMap.put(key2, valuesList2);
    

    在这里

    synchronizedMap.put(key2, valuesList2);
    

    方法调用不依赖于上一次调用的结果

    synchronizedMap.put(key1, valuesList1);
    

    key1

        5
  •  4
  •   Paul Tomblin    15 年前

    在我看来这是正确的。如果要更改任何内容,我将停止使用Collections.synchronizedMap(),并以相同的方式同步所有内容,以使其更清晰。

    还有,我会替换

      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    

    List<String> valuesList = synchronziedMap.get(key);
    if (valuesList == null)
    {
      valuesList = new ArrayList<String>();
      synchronziedMap.put(key, valuesList);
    }
    valuesList.add(value);
    
        6
  •  3
  •   Jai Pandit    8 年前

    同步的方式是正确的。但有一个陷阱

    1. 集合框架提供的同步包装器确保方法调用(即add/get/contains)将互斥运行。

    1. 您可以使用集合框架中可用的映射的并发实现。”ConcurrentHashMap的好处是

    A.它有一个API“putIfAbsent”,可以做同样的事情,但效率更高。

    C您可能已经将map对象的引用传递到了代码库中的其他地方,而您/tean中的其他开发人员最终可能会错误地使用它。也就是说,他可以只添加()或get(),而不锁定地图的对象。因此,他的呼叫不会与您的同步块互斥。但是使用并发实现可以让您安心,因为它 不能被错误地使用/实施。

        7
  •  2
  •   Barend    15 年前

    Google Collections Multimap ,例如,第28页,共页 this presentation .

    如果由于某种原因无法使用该库,请考虑使用 ConcurrentHashMap SynchronizedHashMap ; 它有一个漂亮的颜色 putIfAbsent(K,V) 方法,如果元素列表不存在,则可以使用该方法以原子方式添加元素列表。此外,考虑使用 CopyOnWriteArrayList