线程池和CountDownLatch在Spring项目中配合使用,解决统计、累加业务的例子。
避嫌只写技术,这里的业务就用”统计动物园中所有种类动物数量的总和”,类比代替了。
我要写一个接口,吐出 “动物园所有种类动物的总和”。已知目前有 15种动物,现在有查询每种动物数量的接口,每种动物都要调用RPC接口去别的系统查询。且耗时较高。
工作方案:
根据上面的描述,线性去查询,调用15次RPC接口,时间花费巨大,所以放弃单线程模式。打算使用线程池,进来请求后,线程池分发 15个线程去查每一种动物的数据,返回结果。
具体实现:
1、配置线程池
<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心线程数 -->
<property name="corePoolSize" value="15" />
<!-- 最大线程数 -->
<property name="maxPoolSize" value="30" />
<!-- 队列最大长度 >=mainExecutor.maxSize -->
<property name="queueCapacity" value="30" />
<!-- 线程池维护线程所允许的空闲时间 默认为60s-->
<property name="keepAliveSeconds" value="180" />
<!-- 线程池对拒绝任务(无线程可用)的处理策略 -->
<property name="rejectedExecutionHandler">
<!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 -->
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>
2、Service 中具体逻辑
/** * 查询数量使用的线程池
*/
@Autowired
@Qualifier("threadPool")
private ThreadPoolTaskExecutor threadPool;
public long getAllCount(int accountType, String account) {
try {
// 初始化返回结果
AtomicLong resultValue = new AtomicLong(0);
// 获取所有的动物类型
AllTypeEnum[] enumValues = AllTypeEnum.values();
// 开启栅栏
CountDownLatch countDownLatch = new CountDownLatch(enumValues .length);
// 用线程池分发线程分配处理每一个类型
for (AllTypeEnum tempEnum : enumValues ) {
threadPool.execute(new AnimalCountThread(account, accountType, tempEnum.getType(), resultValue, countDownLatch));
}
// 等所有线程都处理完之后在那返回结果
countDownLatch.await();
return resultValue.get();
} catch (InterruptedException e1) {
log.error("出现线程中断异常", e1);
} catch (Exception e2) {
log.error("出现未知异常", e2);
}
return 0;
}
3、线程的定义
/** * 查询动物数量线程
*
* @author XXX
* @date 2020/05/14
*/
@Data
@Slf4j
public class AnimalCountThread implements Runnable {
/**
* 账号
*/
private String account;
/**
* 账户类型
*/
private int accountType;
/**
* 动物类型 来自枚举
*/
private int type;
/**
* 累加的目标值
*/
private AtomicLong targetValue;
/**
* 栅栏
*/
private CountDownLatch countDownLatch;
/**
* 构造函数
*
* @param account 账号
* @param accountType 账号类型
* @param type 动物类型
* @param targetValue 累加的目标值
* @param countDownLatch 栅栏
*/
public AnimalCountThread (String account, int accountType, int type, AtomicLong targetValue, CountDownLatch countDownLatch) {
this.account = account;
this.accountType = accountType;
this.type = type;
this.targetValue = targetValue;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
AllTypeEnum typeEnum = AllTypeEnum.getEnumByType(getType());
if (typeEnum != null) {
//获取具体业务Bean
CommonAnimalService commonAnimalService = SpringContext.getBean("commonAnimalServiceImpl");
long num = commonAnimalService.countAnimalNum(getAccount(), getWarningType());
targetValue.getAndAdd(num);
}
}catch (Exception e) {
log.error("线程执行出现异常",e);
} finally {
countDownLatch.countDown();
}
}
}
总结 :
1、线程中是无法直接使用注解注入JavaBean的,所以我从Spring容器里拿的。或者也可以不定义这个线程,使用匿名内部类的方法。
2、累计的目标值,直接使用 AtomicLong 省得自己去同步。
3、用CountDownLatch 等所有线程都处理完,主线程再拿返回结果。
4、CountDownLatch 在子线程中,一定要保证被调用到 countDown()。主线程不能捕获到子线程的异常。
5、线程池配置拒绝策略,另外三种都丢弃了任务,所以用交给主线程的这种方法比较适合当前业务。
6、线程池的配置队列长度:要是追求性能的话不能过长。越长耗时越长,接口性能越差。
7、接口最外层要合理使用缓存,缓解压力,在对外RPC接口出还可以配置限流。 由于运用了多线程,快进快出, 限流是为了减小峰值。快进快出的话即使限流。 吞吐量也会比不用“多线程”大。
8、一定要压测一下,对于线程池的配置,也可以根据压测结果,调配。
以上是 线程池和CountDownLatch在Spring项目中配合使用,解决统计、累加业务的例子。 的全部内容, 来源链接: utcz.com/z/516618.html