問題代碼
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
//調用多次,復現多線程的問題
test();
}
}
public static void test() {
//聲明數據源集合
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
//添加100個元素到集合中
list.add(i);
}
//添加數據的集合
List<Integer> list2 = new ArrayList<>();
//使用parallelStream的遍歷方法來添加元素到新的集合
list.parallelStream().forEach(i -> {
list2.add(i);
});
//打印添加元素之後的集合長度
System.out.println(list2.size());
}
執行結果
100
98
100
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:583)
at com.example.demo.collections.LambdaList.test(LambdaList.java:25)
at com.example.demo.collections.LambdaList.main(LambdaList.java:11)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 73
at java.util.ArrayList.add(ArrayList.java:459)
at com.example.demo.collections.LambdaList.lambda$test$0(LambdaList.java:26)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.execLocalTasks(ForkJoinPool.java:1040)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1058)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Process finished with exit code 1
問題分析
代碼很簡單,就是使用parallelStream流遍歷集合,並把元素添加到新的ArrayList集合中,根據執行結果可以看出來,會出現數據條數對不上,或者直接拋異常的問題。
因爲parallelStream是通過ForkJoinPoll來利用多核多線程執行任務,而ArrayList是非線程安全的,所以會有線程安全問題,解決辦法也很簡單,把parallelStream換成stream串行執行遍歷或者把ArrayList換成線程安全的CopyOnWriteArrayList即可。因爲換成stream在數據量較大的情況下會顯著降低速度,所以建議換成CopyOnWriteArrayList。代碼如下。
解決方案
方案一:替換parallelStream爲stream,因爲main方法代碼不變,所以只貼出來test
方法的代碼
public static void test() {
//聲明數據源集合
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
//添加100個元素到集合中
list.add(i);
}
//添加數據的集合
List<Integer> list2 = new ArrayList<>();
//使用stream替換parallelStream
list.stream().forEach(i -> {
list2.add(i);
});
//打印添加元素之後的集合長度
System.out.println(list2.size());
}
執行結果
100
100
100
100
100
方案二:使用CopyOnWriteArrayList來替換ArrayList
public static void test() {
//聲明數據源集合
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
//添加100個元素到集合中
list.add(i);
}
//添加數據的集合,使用CopyOnWriteArrayList替換ArrayList
List<Integer> list2 = new CopyOnWriteArrayList<>();
//使用parallelStream的遍歷方法來添加元素到新的集合
list.parallelStream().forEach(i -> {
list2.add(i);
});
//打印添加元素之後的集合長度
System.out.println(list2.size());
}
執行結果
100
100
100
100
100