gpt4 book ai didi

java - computeIfAbsent 如何随机使 ConcurrentHashMap 失败?

转载 作者:行者123 更新时间:2023-12-01 09:51:28 25 4
gpt4 key购买 nike

我有以下代码,它是一个玩具代码,但可以重现该问题:

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;

public class TestClass3 {
public static void main(String[] args) throws InterruptedException {
// Setup data that we will be playing with concurrently
List<String> keys = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");

HashMap<String, List<Integer>> keyValueMap = new HashMap<>();
for (String key : keys) {
int[] randomInts = new Random().ints(10000, 0, 10000).toArray();
keyValueMap.put(key, stream(randomInts).boxed().collect(toList()));
}

// Entering danger zone, concurrently transforming our data to another shape
ExecutorService es = Executors.newFixedThreadPool(10);
Map<Integer, Set<String>> valueKeyMap = new ConcurrentHashMap<>();
for (String key : keys) {
es.submit(() -> {
for (Integer value : keyValueMap.get(key)) {
valueKeyMap.computeIfAbsent(value, val -> new HashSet<>()).add(key);
}
});
}
// Wait for all tasks in executorservice to finish
es.shutdown();
es.awaitTermination(1, TimeUnit.MINUTES);
// Danger zone ends..

// We should be in a single-thread environment now and safe
StringBuilder stringBuilder = new StringBuilder();
for (Integer integer : valueKeyMap.keySet()) {
String collect = valueKeyMap
.get(integer)
.stream()
.sorted() // This will blow randomly
.collect(Collectors.joining());
stringBuilder.append(collect); // just to print something..
}
System.out.println(stringBuilder.length());
}
}

当我一遍又一遍地运行这段代码时,它通常会毫无异常(exception)地运行并打印一些数字..但是从时间开始(大约 10 次尝试中的 1 次)我会得到类似于以下内容的异常:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 6
at java.util.stream.SortedOps$SizedRefSortingSink.accept(SortedOps.java:369)
at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1556)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
at biz.tugay.TestClass3.main(TestClass3.java:40)

我很确定它与
valueKeyMap.computeIfAbsent(value, val -> new HashSet<>()).add(key);

如果我按如下方式更改此部分,则永远不会出现异常:
synchronized (valueKeyMap) {
valueKeyMap.computeIfAbsent(value, val -> new HashSet<>()).add(key);
}

我在想 computeIfAbsent还在修改 valueKeyMap即使在所有线程都完成之后。

有人可以解释为什么这段代码随机失败,原因是什么?或者是否有完全不同的原因我无法看到,我的假设是错误的 computeIfAbsent是罪魁祸首?

最佳答案

问题不在 computeIfAbsent打电话,而是在.add(key)最后:您可以让多个线程尝试将元素添加到同一个 HashSet,而没有任何东西可以确保安全的并发访问。由于 HashSet 不是线程安全的,因此无法正常工作,并且 HashSet 有时最终会处于损坏状态。稍后,当您尝试遍历 HashSet 以获取字符串时,由于这种损坏状态,它会崩溃。 (从您的异常来看,HashSet 认为其后备数组比实际长,因此它试图访问越界数组元素。)

即使在没有出现异常的运行中,您有时可能最终会“丢弃”本应添加的元素,但并发更新意味着某些更新丢失了。

关于java - computeIfAbsent 如何随机使 ConcurrentHashMap 失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61718067/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com