一、ThreadPoolTaskExecutor
@Autowired
@Qualifier(value = "contractProcessExector")
private ThreadPoolTaskExecutor contractProcessExector;
@Configuration
public class ExecutorConfig {
@Bean("contractProcessExector")
public ThreadPoolTaskExecutor contractProcessExector() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int i = Runtime.getRuntime().availableProcessors();//获取到服务器的cpu内核
executor.setCorePoolSize(1);//核心池大小
executor.setMaxPoolSize(10);//最大线程数
executor.setQueueCapacity(500);//队列程度
executor.setKeepAliveSeconds(300);//线程空闲时间
executor.setThreadNamePrefix("tsak-asyn");//线程前缀名称
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//配置拒绝策略
return executor;
}
}
@Qualifier的作用:把某个对象作为属性赋值给另一个对象
CountDownLatch countDownLatch = new CountDownLatch(integrationContractDOS.size());
integrationContractDOS.forEach(contractDO -> {
try {
ContractProcessCheck contractProcessCheck = contractProcessCheckFactory.getImpl(contractDO);
if(null == contractProcessCheck){
countDownLatch.countDown();
return;
}
....
contractProcessExector.execute(new ContractProcessThread(contractProcess, contractDO, countDownLatch));
} catch (Exception e) {
....
countDownLatch.countDown();
}
});
countDownLatch.await();
CountDownLatch的两种用法,此处使用的是第一种
1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行
2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒
@Slf4j
public class ContractProcessThread implements Runnable {
private ContractProcess contractProcess;
private IntegrationContractDO contractDO;
private CountDownLatch countDownLatch;
public ContractProcessThread (ContractProcess contractProcess, IntegrationContractDO contractDO, CountDownLatch countDownLatch){
this.contractProcess = contractProcess;
this.contractDO = contractDO;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
TraceLogUtil.setTraceLogId();
contractProcess.doContractProcess(contractDO);
} catch (Exception e) {
...
} finally {
countDownLatch.countDown();
}
}
}
二、ThreadPoolExecutor
private static final ExecutorService THREAD_POOL = new ThreadPoolExecutor(10, 20, 20,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("oppo-authApply-pool-%d").build());
orderDetailMap.entrySet().forEach(entry -> {
futureList.add(THREAD_POOL.submit(() -> {
...
}));
});
ThreadPoolTaskExecutor和ThreadPoolExecutor区别
ThreadPoolExecutor是一个java类不提供spring生命周期和参数装配。
ThreadPoolTaskExecutor实现了InitializingBean, DisposableBean ,xxaware等,具有spring特性
1、ThreadPoolTaskExecutor使用ThreadPoolExecutor并增强,扩展了更多特性
2、ThreadPoolTaskExecutor只关注自己增强的部分,任务执行还是ThreadPoolExecutor处理。
3、前者spring自己用着爽,后者离开spring我们用ThreadPoolExecutor爽。
注意:ThreadPoolTaskExecutor 不会自动创建ThreadPoolExecutor需要手动调initialize才会创建
如果@Bean 就不需手动,会自动InitializingBean的afterPropertiesSet来调initialize
ThreadPoolExecutor核心参数说明
1、corePoolSize:核心线程数
* 核心线程会一直存活,及时没有任务需要执行
* 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
* 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
2、queueCapacity:任务队列容量(阻塞队列)
* 当核心线程数达到最大时,新任务会放在队列中排队等待执行
3、maxPoolSize:最大线程数
* 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
* 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
4、 keepAliveTime:线程空闲时间
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0
5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器
* 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
* 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器
三、流处理之flatMap
此时有一个需求,需要判断一个嵌套list中是否包含某个元素等于指定的值,此时如果是用两层for循环则会显得代码很冗余,此时可以用到flatMap将流进行扁平化处理,将两层list转换成一个list再进行判断
代码示例:
Boolean flag = refundDetailList.stream().filter(
refundDetail -> refundDetail.getRepaymentPlanList().stream().filter(repaymentPlan -> "ccc".equals(repaymentPlan.getRefundType())).findAny().isPresent()
).findAny().isPresent();
for(ResDto.RefundDetail refundDetail:refundDetailList){
for(ResDto.RefundDetail.RepaymentPlan repaymentPlan:refundDetail.getRepaymentPlanList()){
if("ccc".equals(repaymentPlan.getRefundType())){
flag = true;
break;
}
}
boolean b = refundDetailList.stream().flatMap(e -> e.getRepaymentPlanList().stream()).anyMatch(e -> "ccc".equals(e.getRefundType()));
这三种处理结果是一样的,但是显然第三种代码比较简单一点
四、springboot自动装配原理
在springboot的启动类中有个组合注解 @SpringbootApplication 此注解中有@EnableAutoConfiguration注解用于开启自动配置,对Jar包下的spring.factorites文件进行扫描,此文件中包含了可以进行自动配置的类,当满足@Conditition注解指定的条件时,便在依赖的支持下进行实例化 注册到spring容器中
在@EnableAutoConfiguration注解中有个@Import注解,此注解导入了AutoConfigurationImportSelector类,该类有一个重要方法,selectImports 几乎涵盖了组件装配的所有处理逻辑、包括获得候选配置类、配置类去重、排除不需要的配置类 过滤等 最终返回符合条件的自动配置类的全限定名数组
如何得到候选的配置类,可以看到所有的配置信息通过getCandidateConfigurations得到,并最终由一个列表保存
getCandidateConfigurations()方法通过SpringFactoriesLoader加载器加载META-INF/spring.factories文件,首先通过这个文件获取到每个配置类的url,再通过这些url将它们封装成Properties对象,最后解析内容存于Map<String,List<String>>中