当前位置:  开发笔记 > 编程语言 > 正文

Java synchronized块与Collections.synchronizedMap

如何解决《Javasynchronized块与Collections.synchronizedMap》经验,为你挑选了4个好方法。

以下代码是否设置为正确同步呼叫synchronizedMap

public class MyClass {
  private static Map> synchronizedMap = Collections.synchronizedMap(new HashMap>());

  public void doWork(String key) {
    List 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 valuesList = new ArrayList();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

根据我的理解,我需要同步块addToMap()来防止另一个线程调用remove()containsKey()在我通过调用之前put()但我不需要同步块doWork()因为另一个线程无法addToMap()remove()返回之前进入synchronized块因为我最初创建了Map与Collections.synchronizedMap().那是对的吗?有一个更好的方法吗?



1> Yuval Adam..:

Collections.synchronizedMap() 保证您要在地图上运行的每个原子操作都将被同步.

但是,必须在块中同步在地图上运行两个(或更多)操作.是的 - 您正在正确同步.


我认为这很有用,因为javadocs明确声明synchronizedMap在地图本身同步,而不是一些内部锁定.如果是这种情况同步(synchronizedMap)将是不正确的.
@Yuval你能更深入地解释一下你的答案吗?你说sychronizedMap以原子方式执行操作,但是如果syncMap使所有操作都是原子的,你为什么还需要自己的synchronized块?你的第一段似乎排除了对第二段的担忧.
为什么有必要使用synchronized块,因为map已经使用了`Collections.synchronizedMap()`?我没有得到第二点.

2> TofuBeer..:

如果您使用的是JDK 6,那么您可能需要查看ConcurrentHashMap

请注意该类中的putIfAbsent方法.


它实际上是JDK 1.5

3> JLR..:

还有就是潜在的代码中的一个微妙的错误.

[ 更新: 因为他正在使用map.remove(),所以这种描述并不完全有效.我第一次错过了这个事实.:(感谢问题的作者指出这一点.我将其余部分保留原样,但更改了主要声明,说有可能存在错误.]

doWork()中,您可以以线程安全的方式从Map获取List值.然而,之后,您在不安全的情况下访问该列表.例如,一个线程可能正在使用doWork()中的列表,而另一个线程在addToMap()中调用synchronizedMap.get(key).add(value ).这两个访问不同步.经验法则是集合的线程安全保证不会扩展到它们存储的键或值.

您可以通过在地图中插入同步列表来解决此问题

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

或者,您可以在doWork()中访问列表时在地图上进行同步:

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

最后一个选项会稍微限制并发性,但IMO会更加清晰.

另外,关于ConcurrentHashMap的快速说明.这是一个非常有用的类,但并不总是适用于同步HashMaps的替代品.引用其Javadocs,

在依赖于线程安全但不依赖于其同步细节的程序中,此类可与Hashtable完全互操作.

换句话说,putIfAbsent()非常适合原子插入,但不保证在该调用期间地图的其他部分不会改变; 它只保证原子性.在您的示例程序中,您依赖于put()s以外的(同步)HashMap的同步详细信息.

最后一件事.:)来自Java Concurrency in Practice的这句精彩报价总能帮助我设计调试多线程程序.

对于可由多个线程访问的每个可变状态变量,必须在保持相同锁的情况下执行对该变量的所有访问.



4> Sergey..:

是的,您正在正确同步.我将更详细地解释这一点.只有在必须依赖于在synchronizedMap对象上的方法调用序列中的后续方法调用中的先前方法调用的结果时,才必须在synchronizedMap对象上同步两个或多个方法调用.我们来看看这段代码:

synchronized (synchronizedMap) {
    if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
    }
    else {
        List valuesList = new ArrayList();
        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已执行该方法

synchronizedMap.containsKey(key)

结果是" true".之后,操作系统已将执行控制切换为thread 2已执行

synchronizedMap.remove(key)

之后,执行控制已切换回thread 1并且已执行,例如

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

相信该synchronizedMap对象包含keyNullPointerException会被抛出,因为synchronizedMap.get(key) 将返回null.如果synchronizedMap对象的方法调用序列不依赖于彼此的结果,那么您不需要同步序列.例如,您不需要同步此序列:

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

这里

synchronizedMap.put(key2, valuesList2);

方法调用不依赖于前面的结果

synchronizedMap.put(key1, valuesList1);

方法调用(它不关心某些线程是否干扰了两个方法调用,例如已经删除了key1).

推荐阅读
低调pasta_730
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有