上文我们主要写了CompletableFuture的基础用法,包括它是什么?如何创建?怎么使用?需要学习参考的可以参看如下文章:
本文我们主要讲述CompletableFuture高阶用法和线程池的使用规范,可以在项目参考灵活运用,配合线程池让代码更简单精巧优雅。
我们都知道创建一个新的线程可以通过继承Thread类或者实现Runnable接口来实现,这两种方式创建的线程在运行结束后会被虚拟机销毁,进行垃圾回收,如果线程数量过多,频繁的创建和销毁线程会浪费资源,降低效率。而线程池的引入就很好解决了上述问题,线程的复用可以更好的创建、维护、管理线程的生命周期,做到资源的有效利用和用户的使用体验提升,也避免了开发人员滥用new关键字创建线程的不规范行为。
特别注意:
1.本案例采用单例线程池实例,整个应用下只创建一次。
2.线程池中的线程数量的增加是动态变化的,大致是根据当前活动线程active和队列数量queue动态调整不断增加的。
3.线程数一旦增加,如果要让增加的线程总数回到默认的初始核心线程数时,只有所有的线程都不在活动中(即使是在小流量的并发请求下,只要存在持续不断,因为线程是来回切换的),且空闲时间达到设置的情况下才会恢复默认情况。
4.如果并发流量很大的情况下,一定要设置队列最大数量,至少保持在500以上,否则不设置容量的情况选容易造成内存溢出和泄露的风险。
5.核心线程一般是cpu的核心数,最大线程数是核心线程的4倍,空闲时间具体根据业务设置即可。
6.由于之前的不规范写法,会导致线程池无法复用,请使用最新的代码进行替换,如有任何疑问可私信联系。
一.使用ThreadPoolExecutor配置线程池
ThreadPoolExecutor是线程池的核心实现类,在JDK1.5引入,位于java.util.concurrent包,由Doug Lea完成,详细可参考如下文案:

1.线程池创建示例一(小流量请求可正常使用,队列满了之后直接丢弃)
温馨提醒:
此线程池的设计在有大量请求时,接口耗时会逐级加大,直接抛出 RejectedExecutionException 异常,最终触发接口请求异常。
问题分析:
a.队列设置错误。在查询统计场景下,需要充分利用线程资源,将任务放入队列会增加任务在队列的等待时间,队列长度越大对系统的伤害越大;
b.拒绝策略设置错误。直接抛出异常会中断主流程,导致部分无效任务(无意义任务)提交,白白浪费系统资源;
public class CustomThreadPoolUtil {
/**
* 自定义线程池
* 单例线程池实例,整个应用只创建一次
*/
private static final ExecutorService CUSTOM_FORWARD_POOL = new ThreadPoolExecutor(
// 核心线程数
8,
// 最大线程数
8*4,
// 线程空闲时间和时间单位
60L, TimeUnit.SECONDS,
/**
* 适合瞬时高吞吐但无缓冲()
*
* 就像是闪送,快递员(生产者)必须把包裹(任务)当面交给收件人(消费者),
* 因没有储物架(没有任务队列)而不能提前送达:如果收件人没有来,快递员则会一直等待收件人出现,
* 同理,收件人也只能等待快递员出现才能当面领到包裹,即强调双方必须同时在场。
*
*/
// new SynchronousQueue<>(),
/**
* 有界队列,防止OOM,任务缓冲
*
* 像是菜鸟物流,快递员(生产者)总是把包裹(任务)放到菜鸟驿站(里面有固定数量的储物架,可理解为任务队列),
* 收件人(消费者)可以在空闲时去菜鸟驿站取件,而不用必须等快递员把包裹送到面前,即强调双方的时间是可以错开的,
* 包裹的送达(入队)和领取(出队)的动作是可以异步进行的。
*/
new LinkedBlockingQueue<>(500),
//自定义线程名称
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("custom-pool-" + count.getAndIncrement());
t.setDaemon(true);
return t;
}
},
// 所有任务必须执行
// new ThreadPoolExecutor.CallerRunsPolicy()
// // 线程队列-任务缓冲
// new LinkedBlockingQueue<>(20),
// // 线程工厂
// Executors.defaultThreadFactory(),
// // 拒绝策略:直接拒绝并抛异常
new ThreadPoolExecutor.AbortPolicy()
);
private CustomThreadPoolUtil() {
}
/**
* 线程池工具
* @return
*/
public static ExecutorService customThreadPool() {
return CUSTOM_FORWARD_POOL;
}
}
2.线程池创建示例二(保证所有的线程都正常执行)
温馨提醒:
1.线程池中的所有资源即使全部耗尽,也只会降级到串行(同步)执行,不会让系统变的更糟糕。
2.线程池有资源可用,那就为主线程分担部分压力;如果没有资源可用,那就由主线程独自完成。
3.在资源充足的情况下,所有任务均有线程池线程完成,主线程一致处于等待状态,存在一定的资源浪费。为了充分利用线程资源,可以在实现任务时考虑让主线程负责执行任意一个任务。。。
public class CustomThreadPoolUtil {
/**
* 自定义线程池
* 单例线程池实例,整个应用只创建一次
*/
private static final ExecutorService CUSTOM_FORWARD_POOL = new ThreadPoolExecutor(
// 核心线程数
8,
// 最大线程数
8*4,
// 线程空闲时间和时间单位
60L, TimeUnit.SECONDS,
/**
* 适合瞬时高吞吐但无缓冲()
*
* 就像是闪送,快递员(生产者)必须把包裹(任务)当面交给收件人(消费者),
* 因没有储物架(没有任务队列)而不能提前送达:如果收件人没有来,快递员则会一直等待收件人出现,
* 同理,收件人也只能等待快递员出现才能当面领到包裹,即强调双方必须同时在场。
*
*/
// new SynchronousQueue<>(),
/**
* 有界队列,防止OOM,任务缓冲
*
* 像是菜鸟物流,快递员(生产者)总是把包裹(任务)放到菜鸟驿站(里面有固定数量的储物架,可理解为任务队列),
* 收件人(消费者)可以在空闲时去菜鸟驿站取件,而不用必须等快递员把包裹送到面前,即强调双方的时间是可以错开的,
* 包裹的送达(入队)和领取(出队)的动作是可以异步进行的。
*/
new LinkedBlockingQueue<>(500),
//自定义线程名称
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("custom-forward-pool-" + count.getAndIncrement());
t.setDaemon(true);
return t;
}
},
// 所有任务必须执行
new ThreadPoolExecutor.CallerRunsPolicy()
// // 线程队列-任务缓冲
// new LinkedBlockingQueue<>(20),
// // 线程工厂
// Executors.defaultThreadFactory(),
// // 拒绝策略:直接拒绝并抛异常
// new ThreadPoolExecutor.AbortPolicy()
);
private CustomThreadPoolUtil() {
}
/**
* 线程池工具
* @return
*/
public static ExecutorService customThreadPool() {
return CUSTOM_FORWARD_POOL;
}
}
二、使用CompletableFuture构建多线程异步查询案例
/**
*此处只展示核心代码,利用多线程异步查询拼接数据
**/
final ExecutorService forwardThread = CustomThreadPoolUtil.customThreadPool();
List<CompletableFuture<?>> futuresToJoin = new ArrayList<>();
CompletableFuture<List<AreaInfoEntity>> future1 =
CompletableFuture.supplyAsync(()->{
LambdaQueryWrapper<AreaInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AreaInfoEntity::getProjectid, pId);
return AreaInfoService.list(queryWrapper);
}, forwardThread);
futuresToJoin.add(future1);
CompletableFuture<List<AreaInfoEntity>> future2 =
CompletableFuture.supplyAsync(() -> areaMapper.getAllAreaAlarmList(params), forwardThread);
futuresToJoin.add(future2);
//返回值为空
CompletableFuture<Void> future3 =
CompletableFuture.runAsync(() -> {
System.currentTimeMillis();
}, forwardThread);
futuresToJoin.add(future3);
//统一执行查询
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futuresToJoin.toArray(new CompletableFuture<?>[0]));
allFutures.join();
//获取执行结果
List<AreaInfoEntity> areaList = future1.get();
Map<String, AreaInfoEntity> areaInfoEntityMap = future2.get().stream()
.collect(Collectors.toMap(ServDefenceAreaInfoEntity::getId,
Function.identity(), (key1, key2) -> key2));
三、使用日志持续监控线程池使用情况
//输出线程池状态监控(线程池队列大于100时输入)
ThreadPoolExecutor pool = (ThreadPoolExecutor) forwardThread;
if (pool.getQueue().size() > 100) {
log.warn("[CUSTOM_POOL线程池监控] active={}, queue={}, completed={}, poolSize={}",
pool.getActiveCount(),
pool.getQueue().size(),
pool.getCompletedTaskCount(),
pool.getPoolSize());
}


CompletableFuture与线程池优化实践

171万+

被折叠的 条评论
为什么被折叠?



