线程池和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

回到顶部