1.问题
List<List<Object>> splitList = CollUtil.split(dataList, 800);
List<User> resultList =new ArrayList<>();
// 使用parallelStream输出切割后的结果,每个子列表的大小
splitList.parallelStream().forEach(sublist -> {
List<User> users = userMapper.selectByIds(sublist);
resultList.addAll(users);
});
上面这段代码中,resultList是一个ArrayList,而ArrayList是非线程安全的。在并行流的操作中,多个线程可能会同时调用addAll方法,这可能导致竞态条件,可能导致数据丢失或抛出ConcurrentModificationException,解决方案有下面三种,推荐第三种。
2.解决方案
-
使用线程安全的集合:
使用Collections.synchronizedList
来包装你的ArrayList
。List<User> resultList = Collections.synchronizedList(new ArrayList<>()); splitList.parallelStream().forEach(sublist -> { List<User> users = userMapper.selectByIds(sublist); resultList.addAll(users); });
-
使用并发集合:
使用CopyOnWriteArrayList
或其他适合的并发集合。List<User> resultList = new CopyOnWriteArrayList<>(); splitList.parallelStream().forEach(sublist -> { List<User> users = userMapper.selectByIds(sublist); resultList.addAll(users); });
-
使用collect操作:
使用流的collect
方法来收集结果,这样可以避免手动处理并发问题。List<User> resultList = splitList.parallelStream() .flatMap(sublist -> userMapper.selectByIds(sublist).stream()) .collect(Collectors.toList());
第三种方法通常是推荐的,因为它更符合流的使用范式,并且处理好了并发问题。