使用ConcurrentHashMap,何时需要同步?

时间:2013-02-13 10:40:05

标签: java concurrency concurrenthashmap

我有一个ConcurrentHashMap,我在其中执行以下操作:

sequences = new ConcurrentHashMap<Class<?>, AtomicLong>();

if(!sequences.containsKey(table)) {
    synchronized (sequences) {
        if(!sequences.containsKey(table))
            initializeHashMapKeyValue(table);
    }
}

我的问题是 - 是否有必要进行额外的

if(!sequences.containsKey(table))

检查synschronized块内部,以便其他线程不会初始化相同的hashmap值?

也许检查是必要的,我做错了?我正在做的事似乎有点傻,但我认为这是必要的。

6 个答案:

答案 0 :(得分:21)

ConcurrentHashMap上的所有操作都是线程安全的,但线程安全操作不可组合。你试图让原子成为一对操作:检查地图中的某些东西,如果它不在那里,就把东西放在那里(我假设)。所以你的问题的答案是,你需要再次检查,你的代码看起来还不错。

答案 1 :(得分:17)

您应该使用ConcurrentMap的{​​{3}}方法。

ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong> ();

public long addTo(String key, long value) {
  // The final value it became.
  long result = value;
  // Make a new one to put in the map.
  AtomicLong newValue = new AtomicLong(value);
  // Insert my new one or get me the old one.
  AtomicLong oldValue = map.putIfAbsent(key, newValue);
  // Was it already there? Note the deliberate use of '!='.
  if ( oldValue != newValue ) {
    // Update it.
    result = oldValue.addAndGet(value);
  }
  return result;
}

对于我们中间的功能性纯粹主义者,上述内容可以简化(或可能是复杂化):

public long addTo(String key, long value) {
    return map.putIfAbsent(key, new AtomicLong()).addAndGet(value);
}

在Java 8中,我们可以避免不必要地创建AtomicLong

public long addTo8(String key, long value) {
    return map.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(value);
}

答案 2 :(得分:4)

您无法使用 ConcurrentHashMap 获得排他锁定。在这种情况下,您最好使用Synchronized HashMap。

如果对象尚未存在,则已经有一个原子方法放入ConcurrentHashMap; putIfAbsent

答案 3 :(得分:1)

我看到你在那里做了什么;-)问题是你自己看到了吗?

首先,您使用了所谓的“双重检查锁定模式”。你有快速路径(第一个包含),如果它满足则不需要同步,慢速路径,因为你做复杂的操作必须同步。您的操作包括检查地图内是否有东西,然后放置/初始化它。因此,ConcurrentHashMap对于单个操作是线程安全的并不重要,因为您执行两个必须被视为单元的简单操作,因此这个同步块是正确的,实际上它可以通过其他任何内容进行同步,例如this。 / p>

答案 4 :(得分:1)

在Java 8中,您应该能够用.computeIfAbsent替换双重检查的锁:

sequences.computeIfAbsent(table, k -> initializeHashMapKeyValue(k));

答案 5 :(得分:0)

使用以下内容创建名为dictionary.txt的文件:

a
as
an
b
bat
ball

我们在这里: 以“a”开头的单词计数:3

以“b”开头的单词数:3

总字数:6

现在执行以下程序:java WordCount test_dictionary.txt 10

public class WordCount {
String fileName;

public WordCount(String fileName) {
    this.fileName = fileName;
}

public void process() throws Exception {
    long start = Instant.now().toEpochMilli();

    LongAdder totalWords = new LongAdder();
    //Map<Character, LongAdder> wordCounts = Collections.synchronizedMap(new HashMap<Character, LongAdder>());
    ConcurrentHashMap<Character, LongAdder> wordCounts = new ConcurrentHashMap<Character, LongAdder>();

    Files.readAllLines(Paths.get(fileName))
        .parallelStream()
        .map(line -> line.split("\\s+"))
        .flatMap(Arrays::stream)
        .parallel()
        .map(String::toLowerCase)
        .forEach(word -> {
            totalWords.increment();
            char c = word.charAt(0);
            if (!wordCounts.containsKey(c)) {
                wordCounts.put(c, new LongAdder());
            }
            wordCounts.get(c).increment();
        });
    System.out.println(wordCounts);
    System.out.println("Total word count: " + totalWords);

    long end = Instant.now().toEpochMilli();
    System.out.println(String.format("Completed in %d milliseconds", (end - start)));
}

public static void main(String[] args) throws Exception {
    for (int r = 0; r < Integer.parseInt(args[1]); r++) {
        new WordCount(args[0]).process();
    }
}

}

您会看到计数变化如下所示:

{a = 2,b = 3}

总字数:6

以77毫秒完成

{a = 3,b = 3}

总字数:6

现在在第13行注释掉ConcurrentHashMap,取消注释它上面的行并再次运行程序。

你会看到确定性的计数。