Java 使用ConcurrentHashMap,何时需要同步?

Java 使用ConcurrentHashMap,何时需要同步?,java,concurrency,concurrenthashmap,Java,Concurrency,Concurrenthashmap,我有一个ConcurrentHashMap,我在其中执行以下操作: sequences = new ConcurrentHashMap<Class<?>, AtomicLong>(); if(!sequences.containsKey(table)) { synchronized (sequences) { if(!sequences.containsKey(table)) initializeHashMapKeyVal

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

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

if(!sequences.containsKey(table)) {
    synchronized (sequences) {
        if(!sequences.containsKey(table))
            initializeHashMapKeyValue(table);
    }
}
检查synschronized块内部,以便其他线程不会初始化相同的hashmap值


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

你不能用ConcurrentHashMap获得独占锁。在这种情况下,最好使用同步HashMap


如果对象不在ConcurrentHashMap中,那么已经有了一个原子的方法放在ConcurrentHashMap中
putIfAbsent

ConcurrentHashMap上的所有操作都是线程安全的,但线程安全的操作是不可组合的。你试图让原子成为一对操作:检查地图上的东西,如果它不在那里,就把东西放在那里(我猜)。因此,您的问题的答案是“是”,您需要再次检查,您的代码看起来正常。

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

首先,你使用了一种叫做“双重检查锁定模式”的东西。其中,快速路径(第一个包含)在满足条件时不需要同步,而慢速路径由于执行复杂操作而必须同步。你的操作包括检查地图中是否有东西,然后把东西放在那里/初始化它。因此,ConcurrentHashMap对于单个操作来说是线程安全的并不重要,因为您执行两个简单的操作,必须将其视为一个单元。因此,是的,此同步块是正确的,实际上它可以通过任何其他方法进行同步,例如,
您应该使用
ConcurrentMap
的方法

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;
}
在Java 8中,我们可以避免不必要地创建一个
AtomicLong

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

在Java 8中,您应该能够使用
.computeIfAbsent

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

创建一个名为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();
    }
}
公共类字数{
字符串文件名;
公共字数(字符串文件名){
this.fileName=文件名;
}
public void进程()引发异常{
长时间开始=瞬间.now().toEpochMilli();
LongAdder totalWords=新的LongAdder();
//Map wordCounts=Collections.synchronizedMap(新HashMap());
ConcurrentHashMap字计数=新的ConcurrentHashMap();
Files.readAllLines(path.get(fileName))
.parallelStream()
.map(直线->直线分割(\\s+))
.flatMap(数组::流)
.parallel()
.map(字符串::toLowerCase)
.forEach(word->{
totalWords.increment();
字符c=字字符(0);
如果(!wordCounts.containsKey(c)){
put(c,新的LongAdder());
}
get(c.increment();
});
System.out.println(字数);
System.out.println(“总字数:“+totalWords”);
long end=Instant.now().toEpochMilli();
System.out.println(String.format(“在%d毫秒内完成”,“结束-开始”);
}
公共静态void main(字符串[]args)引发异常{
for(int r=0;r
}

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

{a=2,b=3}

总字数:6

在77毫秒内完成

{a=3,b=3}

总字数:6

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


您将看到确定性计数。

当您想要实现ConcurrentHashMap时,此实例上同步对象锁的要求是什么。无法在映射上获得锁的事实并不妨碍您同步对映射的访问。同意。。。但它是否增加了任何价值。事实上,您会有一个错误的印象。ConcurrentMap将实现更好的并发性-因此,如果
sequences.containsKey(table)
通常是正确的,如果性能是一个问题,使用并发映射仍然是有意义的。这是一个有效的建议。在大多数情况下,避免进行过多的查找。您可以跳过额外检查的一种情况是,您不介意对相同的
值多次初始化hashmapkeyvalue(table)
。这可能是因为
initializeHashMapKeyValue
没有副作用,而且价格相当便宜。如果
initializeHashMapKeyValue
花费大量时间或有副作用,那么使用OP模式可能是有意义的。@assylias-如果副作用不可避免,我想我会同意,但有很多方法可以解决使用factory methods等方法避免/回避副作用。我会尽可能使用此模式,因为它保证不存在竞争条件,而双重检查锁定模式以存在非常微妙的问题而闻名。双重检查锁定可能存在问题,但在这个特定示例中不存在。请注意,我并不是说您的方法不合适-我只是说OP的方法在某些情况下可能更好。如果(oldValue!=null)返回oldValue.addAndGet(value),则需要
;否则返回值
或等效值,因为当确实不存在时,
oldValue
将为
null
。这个混乱的api不返回
newValue
,这也阻止了尼斯的简化版本。
a
as
an
b
bat
ball
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();
    }
}