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

时间:2017-01-28 02:22:11

标签: java concurrency race-condition java.util.concurrent concurrenthashmap

我想解析一个文本文件并计算一些令牌。文件逐行读取,每行分成标记。令牌放在一个列表中,然后由一个计算它们的方法处理。令牌存储在并发的散列映射中,其中令牌作为键,金额作为值。我还需要为最高的wordcount排序。

但看起来我错过了一些东西,因为我在计算上得到了不同的结果。

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);
    }
}

我的错误在哪里,如果可能,我该如何解决?

1 个答案:

答案 0 :(得分:0)

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

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

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

要修改它而不更改初始方法,您可以将AtomicInteger用作wordCount

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

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

第2步获取AtomicInteger的引用,该引用对应于给定的令牌并递增它。此操作也是线程保存。