我有以下代码,这是一个玩具代码,但可以重现该问题:
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());
}
}
当我一遍又一遍地运行此代码时,它通常将在没有任何异常的情况下运行,并且会打印一些数字。但是从时间的角度(大约每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
应当承担责任是错误的?
我有以下代码,这是一个玩具代码,但可以重现该问题:import java.util。*;导入java.util.concurrent.ConcurrentHashMap;导入java.util.concurrent.ExecutorService; ...
问题不在computeIfAbsent
调用中,而是在.add(key)
末尾:您可以有多个线程试图将元素添加到同一HashSet中,而没有任何东西可以确保安全的并发访问。由于HashSet不是线程安全的,因此无法正常工作,并且HashSet有时会处于损坏状态。稍后,当您尝试遍历HashSet以获取字符串时,由于此损坏状态而使其崩溃。 (从您的异常情况来看,HashSet认为其支持数组比实际更长,因此它试图访问越界数组元素。)
synchronized
时您没有得到例外的事实应该已经弄清了问题所在。如前所述,问题实际上是HashSet
,因为它不是线程安全的。集合的文档中也对此进行了说明。