java多线程编码应用(二)——java多线程CompletableFuture高阶应用和单例线程池ThreadPoolExecutor的使用优化和监控

CompletableFuture与线程池优化实践

上文我们主要写了CompletableFuture的基础用法,包括它是什么?如何创建?怎么使用?需要学习参考的可以参看如下文章:

java多线程之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完成,详细可参考如下文案:

Java线程池ThreadPoolExecutor详解

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());
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值