关于线程池异步线程中再次获取线程池资源的问题

 更新时间:2024年08月20日 10:33:56   作者:BlueKitty1210  
这篇文章主要介绍了关于线程池异步线程中再次获取线程池资源的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

问题描述

在线上发生的一次问题,在场景中有这样一个业务,需要异步执行一个主任务,主任务中又包含着N个子任务,为了整个主任务能够快速处理,又将子任务按照数量获取线程资源异步处理,即异步线程A中再异步调用A1,A2,A3. A可能同时存在多个.

实际场景中,由于系统线程池分配数量较小,且一段时间内先后启动了多个主任务,耗时的主任务中又用子任务取申请线程导致线程池资源耗尽

问题原因

1. 主任务是从线程池中获取的线程资源,同时主任务比较耗时​

2. 每个主任务中包含的N的子任务,会再申请线程,处理完毕释放回线程池

3. 启动了多个主任务时,每个主任务在未结束之前,都会占用自身一个线程不会释放,消耗一个线程池资源

4. 后期频繁启动主任务,可能使数量=线程池线程数,此时子任务无法再从线程池获得资源,就进入队列等待

5. 最终结果就造成了每个主任务都占用线程,但主任务内的子任务无法获取线程,线程池瘫痪不可用

问题复现

package test;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @title 线程池异步线程中再次获取线程池资源的问题
 * @author Xingbz
 * @description
 *  记;
 *
 *  究其原因在于:
 *      
 * @createDate 2020-7-17
 */
@Slf4j
public class TestWork {

    private static final ThreadPoolTaskExecutor EXECUTOR;

    static {
        EXECUTOR = myExecutor();
    }

    /** 初始化线程池 */
    public static ThreadPoolTaskExecutor myExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(5);
        // 最大线程数
        executor.setMaxPoolSize(20);
        // 排队任务队列
        executor.setQueueCapacity(100);
        // 线程名称前缀
        executor.setThreadNamePrefix("异步线程-");
        // 队列满后拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 线程最大回收时间
        executor.setKeepAliveSeconds(100);
        // 初始化线程
        executor.initialize();
        return executor;
    }

    /** 模拟测试 */
    public static void main(String[] args) throws Exception {
        // 主任务数量
        int mainJobNum = 20;

        CountDownLatch mainDownLatch = new CountDownLatch(mainJobNum);

        for (int i = 0; i < mainJobNum; i++) {
            // 主任务编号, 方便区分
            int index = i + 1;

            // 模拟每1秒开始一个主任务
            TimeUnit.SECONDS.sleep(1);

            EXECUTOR.submit(() -> {
                try {
                    log.debug("\t执行主任务" + index);

                    // 每个主任务随机包含N个子任务, 再异步调用线程池资源处理
                    int subJobNum = RandomUtils.nextInt(2, 3);
                    subJobWorkAsync(subJobNum, index);
                } finally {
                    mainDownLatch.countDown();
                }
            });
        }

        mainDownLatch.await();
        EXECUTOR.shutdown();
        log.info("完成所有任务 > > >");
    }

    /** 异步执行子任务 */
    private static void subJobWorkAsync(int subJobNum, int index) {
        CountDownLatch subDownLatch = new CountDownLatch(subJobNum);
        for (int j = 0; j < subJobNum; j++) {
            EXECUTOR.submit(() -> {
                try {
                    log.warn("\t\t\t执行一个" + index + "的子任务");
                    // 每个子任务模拟耗时
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    subDownLatch.countDown();
                }
            });
        }

        try {
            subDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行代码,结果如下:

可以看到,线程池很快就被主任务耗尽, 导致子任务无法执行.

解决方案

1. 异步线程中不能再获取异步线程

既然主方法是异步执行了,那么其中的子任务也相对不那么要求时间.此处是我为了业务给另外一个业务复用导致了线程再调线程

2. 如果异步中确实需要再获取异步线程,需要使用新的线程池. 不能再使用自身的线程池

这是当前我们的解决方案,在系统中又单独构建了一个线程池负责子任务的业务

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 流式图表拒绝增删改查之kafka核心消费逻辑上篇

    流式图表拒绝增删改查之kafka核心消费逻辑上篇

    这篇文章主要为大家介绍了流式图表拒绝增删改查之kafka核心消费逻辑详解的上篇,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Java实现简单计算器小程序

    Java实现简单计算器小程序

    这篇文章主要为大家详细介绍了Java实现简单计算器小程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • Spring中Xml属性配置的解析全过程记录

    Spring中Xml属性配置的解析全过程记录

    这篇文章主要给大家介绍了关于Spring中Xml属性配置的解析全过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Java.SE数组的一些常见练习题

    Java.SE数组的一些常见练习题

    数组可以看成是相同类型元素的一个集合,在内存中是一段连续的空间,这篇文章主要给大家介绍了关于Java.SE数组的一些常见练习题,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • Java并发编程中的ReentrantLock类详解

    Java并发编程中的ReentrantLock类详解

    这篇文章主要介绍了Java并发编程中的ReentrantLock类详解,ReentrantLock是juc.locks包中的一个独占式可重入锁,相比synchronized,它可以创建多个条件等待队列,还支持公平/非公平锁、可中断、超时、轮询等特性,需要的朋友可以参考下
    2023-12-12
  • Java反射之通过反射获取一个对象的方法信息(实例代码)

    Java反射之通过反射获取一个对象的方法信息(实例代码)

    下面小编就为大家带来一篇Java反射之通过反射获取一个对象的方法信息(实例代码)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10
  • Spring整合mybatis、springMVC总结

    Spring整合mybatis、springMVC总结

    这篇文章主要为大家详细介绍了Java整合Mybatis,SpringMVC,文中有详细的代码示例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2023-05-05
  • 详解Spring全局异常处理的三种方式

    详解Spring全局异常处理的三种方式

    这篇文章主要介绍了详解Spring全局异常处理的三种方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • SpringBoot整合微信登录功能的实现方案

    SpringBoot整合微信登录功能的实现方案

    今天通过本文给大家分享微信登录与SpringBoot整合过程,微信扫描登录实现代码知道扫描后点击登录的全部过程,本文给大家介绍的非常详细,需要的朋友可以参考下
    2021-10-10
  • java实现将ftp和http的文件直接传送到hdfs

    java实现将ftp和http的文件直接传送到hdfs

    前面几篇文章,我们已经做了很好的铺垫了,几个要用到的工具我们都做了出来,本文就是将他们集合起来,说下具体的用法,小伙伴们可以参考下。
    2015-03-03

最新评论