为什么在使用并发哈希映射的这段代码中仍然存在某种竞争条件?

Why is there still some sort of race condition in this code using concurrent hashmaps?

我想解析一个文本文件并计算一些标记。逐行读取文件,每一行都被分成标记。令牌被放在一个列表中,然后由计算它们的方法处理。令牌存储在并发哈希图中,令牌作为键,金额作为值。我还需要将其排序以获得最高字数。

但我好像漏掉了什么,因为我在计数时得到了不同的结果。

private ConcurrentHashMap<String, Integer> wordCount = new ConcurrentHashMap<>();
private ExecutorService executorService = Executors.newFixedThreadPool(4);

private void parseFile(String file) {

    try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),
        StandardCharsets.ISO_8859_1))) {
        String line;

        ArrayList<String> tokenListForThread;
        while ((line = reader.readLine()) != null) {
            tokenListForThread = new ArrayList<>();
            StringTokenizer st = new StringTokenizer(line, " .,:!?", false);
            while (st.hasMoreTokens()) {
                tokenListForThread.add(st.nextToken());
            }
            startThreads(tokenListForThread);
        }
        reader.close();
        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(-1);
    }
    printWordCount();
}

private void startThreads(ArrayList<String> tokenList) {
    executorService.execute(() -> countWords(tokenList));
}

private void countWords(ArrayList<String> tokenList) {
    for (String token : tokenList) {
        int cnt = wordCount.containsKey(token) ? wordCount.get(token) : 0;
        wordCount.put(token, cnt + 1);
        /*if (wordCount.containsKey(token)){
            wordCount.put(token, wordCount.get(token)+ 1 );
        } else{
            wordCount.putIfAbsent(token, 1);
        }*/
    }
}

private void printWordCount() {
    ArrayList<Integer> results = new ArrayList<>();

    for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
        results.add(entry.getValue());
    }

    results.sort(Comparator.reverseOrder());

    for (int i = 0; i < 10; i++) {
        Integer tmp = results.get(i);
        System.out.println(tmp);
    }
}

我的错误在哪里,如果可能我该如何改正?

令牌计数递增应该是原子的,但它不是

int cnt = wordCount.containsKey(token) ? wordCount.get(token) : 0;
wordCount.put(token, cnt + 1);

令牌列表中具有相同令牌的两个线程可能会同时获得相同的 cnt,然后将其递增并放回。即总计数可能低于真实计数。

要在不更改初始方法的情况下修复它,您可以使用 AtomicInteger 作为 wordCount

wordCount.putIfAbsent(token, new AtomicInteger());
wordCount.get(token).incrementAndGet();

第 1 步 如果还没有 token,但您要添加它。令牌和 zero 计数应该放在地图上。 putIfAbsent 方法是原子的,可以避免并发问题。

步骤 2 获取对 AtomicInteger 的引用,它对应于给定的标记并递增它。这个操作也是线程保存的。