为什么在使用并发哈希映射的这段代码中仍然存在某种竞争条件?
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
的引用,它对应于给定的标记并递增它。这个操作也是线程保存的。
我想解析一个文本文件并计算一些标记。逐行读取文件,每一行都被分成标记。令牌被放在一个列表中,然后由计算它们的方法处理。令牌存储在并发哈希图中,令牌作为键,金额作为值。我还需要将其排序以获得最高字数。
但我好像漏掉了什么,因为我在计数时得到了不同的结果。
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
的引用,它对应于给定的标记并递增它。这个操作也是线程保存的。