case一、并行流向hashmap插入数据
背景:nps服务启动的时候,会并发将策略类注册到静态map集合中
代码:
public class DemoRegistry implements InitializingBean, BeanFactoryAware {
private static Map<Integer, TestService> registryMap = new HashMap<>(2);
private BeanFactory beanFactory;
public static TestService getTestService(Integer index) {
return registryMap.get(index);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
Map<String, TestService> map = ((ListableBeanFactory) beanFactory).getBeansOfType(TestService.class);
map.values().parallelStream().forEach(it -> registryMap.put(it.demoType().getType(), it));
String str = String.format("已注册了%s条Demo测试数据. map[ %s ]",map.size(),
map.entrySet().stream().map(entry -> entry.getValue().demoType().getType().toString())
.collect(Collectors.joining(",")));
System.out.println(str);
}
}
解决方案:
1、使用 ConcurrentHashMap
ConcurrentHashMap 是线程安全的实现,它在并发环境下使用分段锁来实现高效的并发访问。
2、使用同步块
你也可以使用同步块来确保 put 操作的线程安全,但这会降低并行性能
3、禁用并行流
如果数据量不大,或者并行并没有明显的性能提升,可以使用普通的流来避免并发问题:
注意:Java 8 中的 parallelStream 是使用自带的线程池来执行并行操作的。
详细解释
在 Java 8 中,parallelStream 通过 ForkJoinPool 来实现并行流的处理。默认情况下,ForkJoinPool中的公共线程池(common pool)用于并行流的任务执行。
ForkJoinPool
ForkJoinPool.commonPool():这是一个公共的、共享的线程池,默认情况下由所有并行流共享使用。
默认线程数:默认情况下,ForkJoinPool.commonPool() 的大小是可用处理器数(CPU 核心数)减去 1。
case二:系统未制定线程池进行异步,使用了功能线程池
private static void problemCode() {
CompletableFuture<Void> waybillFuture = CompletableFuture.runAsync(() -> {
//关联运单信息
initOrderWaybillInfo(wmOrderResult, finalWmOrderDTO);
//设置站长信息
initWmOrderHorseMan(wmOrderResult, finalWmOrderDTO);
});
CompletableFuture<Void> mealFuture = CompletableFuture.runAsync(() -> {
try {
// 商家出餐慢标签和预判责结果
isPoiMealSlowOutAndPreResponsubleParty(id, finalWmOrderDTO, wmOrderResult);
} catch (Exception e) {
LOGGER.error("{} 商家出餐慢标签和预判责结果出现异常", LOG_PREFIX, e);
}
});
}