ThreadPoolExecutor-RejectedExecutionException报拒绝执行怎么办?

写个简单使用:

Task类、MyThreadFactory类参见:https://blog.youkuaiyun.com/SJZYLC/article/details/98099977

private static void simple(){
        ThreadFactory namedThreadFactory = new MyThreadFactory();
        int queueCapacity = 3, corePoolSize=2, maximumPoolSize=4;
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(queueCapacity);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,10,
                TimeUnit.SECONDS,arrayBlockingQueue);
        for(int i=1;i<=120;i++){
                Thread thread = namedThreadFactory.newThread(new Task(i));
                threadPoolExecutor.execute(thread);
                System.out.printf("i:%d, queueSize:%d, poolSize:%d, coreSize:%d, maxSize:%d\n",
                        i, arrayBlockingQueue.size(), threadPoolExecutor.getPoolSize(),
                        threadPoolExecutor.getCorePoolSize(), threadPoolExecutor.getMaximumPoolSize());
        }
        threadPoolExecutor.shutdown();
        while (true){
            if(threadPoolExecutor.isTerminated()){
                System.out.println("over");
                break;
            }
        }
        System.out.println( );
    }

for循环执行120次,也就是向线程池添加120个任务,结果报了异常:

Exception in thread "main" java.util.concurrent.RejectedExecutionException

因为队列大小为3,线程池最大为4,小于要添加的任务,所以会报这个拒绝执行异常。

如何避免异常呢?

任务数不确定,即使确定,也不能创建一个特别大容量的线程池。

由于线程池会不断从队列中取线程任务进行执行,而队列是阻塞的,所以我们可以做一个判断:当线程池中的线程数等于当前总容量的时候,就向阻塞队列添加线程。队列满了,就会阻塞,直到有新的空间,就会继续向队列里面添加新元素。

private static void simple(){
        ThreadFactory namedThreadFactory = new MyThreadFactory();
        int queueCapacity = 3, corePoolSize=2, maximumPoolSize=4;
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(queueCapacity);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,10,
                TimeUnit.SECONDS,arrayBlockingQueue);
        for(int i=1;i<=120;i++){
            Thread thread = namedThreadFactory.newThread(new Task(i));
            ////判断当前线程池是否已满    
            if((threadPoolExecutor.getPoolSize()+arrayBlockingQueue.size())>=(queueCapacity+maximumPoolSize)){
                try {
                    arrayBlockingQueue.put(thread);
                    System.out.printf("队列中添加线程 i:%d, queueSize:%d, poolSize:%d, coreSize:%d, maxSize:%d\n",
                            i, arrayBlockingQueue.size(), threadPoolExecutor.getPoolSize(),
                            threadPoolExecutor.getCorePoolSize(), threadPoolExecutor.getMaximumPoolSize());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                threadPoolExecutor.execute(thread);
                System.out.printf("i:%d, queueSize:%d, poolSize:%d, coreSize:%d, maxSize:%d\n",
                        i, arrayBlockingQueue.size(), threadPoolExecutor.getPoolSize(),
                        threadPoolExecutor.getCorePoolSize(), threadPoolExecutor.getMaximumPoolSize());
            }
        }
        threadPoolExecutor.shutdown();
        while (true){
            if(threadPoolExecutor.isTerminated()){
                System.out.println("over");
                break;
            }
        }
        System.out.println( );
    }

还有一种优雅的方式,使用RejectedExecutionHandler处理。

当execute()方法不能接受某个任务时,会调用rejectedExecution()方法。RejectedExecutionHandler是个接口,我们在实现里面处理,当调用这个方法时,我们想阻塞队列添加元素。

public class MyRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            //阻塞
            System.out.format("handler处理,向阻塞队列排队,%s \n", r);
            executor.getQueue().put(r);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

之前的方法为:

private static void simple(){
        ThreadFactory namedThreadFactory = new MyThreadFactory();
        int queueCapacity = 3, corePoolSize=2, maximumPoolSize=4;
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(queueCapacity);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,10,
                TimeUnit.SECONDS,arrayBlockingQueue,new MyRejectedExecutionHandler());
        for(int i=1;i<=120;i++){
            Thread thread = namedThreadFactory.newThread(new Task(i));
            threadPoolExecutor.execute(thread);
            System.out.printf("i:%d, queueSize:%d, poolSize:%d, coreSize:%d, maxSize:%d\n",
                    i, arrayBlockingQueue.size(), threadPoolExecutor.getPoolSize(),
                    threadPoolExecutor.getCorePoolSize(), threadPoolExecutor.getMaximumPoolSize());
        }
        threadPoolExecutor.shutdown();
        while (true){
            if(threadPoolExecutor.isTerminated()){
                System.out.println("over");
                break;
            }
        }
        System.out.println( );
    }

也就是构造ThreadPoolExecutor时候,传入RejectedExecutionHandler对象。

<think>嗯,我现在需要分析用户提供的这段Java代码,看看有没有什么问题。代码是使用Spring的@Configuration来配置一个线程池,具体是ThreadPoolExecutor。让我仔细看看。 首先,用户创建了一个ThreadPoolConfig类,里面有一个方法taskFlowThreadPool,用@Bean注解,并指定了名字"taskFlowThreadPool"。这个方法返回一个ThreadPoolExecutor实例。构造函数的参数分别是核心线程数5,最大线程数10,空闲时间10分钟,任务队列是ArrayBlockingQueue容量100,使用默认的线程工厂和拒绝策略AbortPolicy。 那我得想想,常见的线程池配置问题有哪些。可能的问题点包括队列容量、拒绝策略、线程工厂、参数设置是否合理,以及是否缺少监控或自定义配置。 首先看队列容量。这里用的是ArrayBlockingQueue,容量100。如果并发量很高,当任务数超过核心线程数时,任务会进入队列,队列满了之后才会创建新线程,直到最大线程数。但这里最大线程数是10,核心是5。如果突然有很多任务,比如瞬间来了200个任务,那么队列只能存100,这时候会创建到最大线程10,然后还有任务进来就会触发拒绝策略AbortPolicy,也就是抛出异常。这可能在高负载时导致任务丢失,是否应该根据实际场景调整队列大小或者使用其他拒绝策略? 然后是拒绝策略的问题。AbortPolicy确实会抛出RejectedExecutionException,但可能用户希望更友好的处理方式,比如CallerRunsPolicy,让主线程自己执行,避免任务丢失。或者至少应该记录日志,方便排查问题,但这里没有自定义的拒绝策略,直接用了默认的Abort。 线程工厂用的是Executors.defaultThreadFactory(),这样创建的线程名字可能不够友好,比如默认的pool-1-thread-1这样的名称,不利于监控和排查问题。通常建议自定义线程工厂,给线程命名,方便识别。 参数设置是否合理?比如核心线程数5,最大10,空闲10分钟回收。核心线程是否允许回收?默认情况下,ThreadPoolExecutor的核心线程是不会被回收的,除非设置了allowCoreThreadTimeOut。但这里没有设置,所以即使线程空闲,核心的5个线程会一直存在。如果应用需要动态调整,或者希望在没有任务时释放资源,可能需要配置allowCoreThreadTimeOut为true,但需要显式调用方法。 另外,是否应该给线程池添加监控?比如通过暴露端点,或者记录线程池的运行状态,如队列大小、活跃线程数等,方便运维。 还有,这里使用了@Bean注解,确保线程池被Spring管理,但有没有可能在其他地方重复定义了同名的Bean?不过用户已经指定了Bean的名字,应该没问题。 总结下来,可能的问题点包括: 1. 队列容量可能过小,导致任务被拒绝。 2. 拒绝策略AbortPolicy可能不够合适。 3. 线程工厂未自定义,线程名称不易识别。 4. 核心线程无法回收,可能浪费资源。 5. 缺少监控和参数调整的考虑。 6. 可能需要配置allowCoreThreadTimeOut来允许核心线程超时回收。</think>该线程池配置存在以下几个潜在问题,需要根据实际业务场景进行评估和优化: --- ### 一、队列容量与拒绝策略风险 ```java new ArrayBlockingQueue<>(100) // 队列容量固定为100 new ThreadPoolExecutor.AbortPolicy() // 直接抛出异常 ``` **问题分析**: - **队列满载风险**:当核心线程5个全忙时,新任务进入队列。若队列存满100个任务,且此时线程数尚未达到最大10个,会继续创建新线程处理队列中的任务。但若瞬间流量突增,队列和最大线程都无法处理时,触发`AbortPolicy`直接抛出`RejectedExecutionException` - **数据丢失隐患**:默认拒绝策略会导致任务无法提交,关键业务场景可能丢失任务 **改进方案**: - 根据业务峰值评估队列容量,或使用动态队列(如`LinkedBlockingQueue`无界队列,但有OOM风险需谨慎) - 改用`CallerRunsPolicy`让主线程执行任务,或自定义拒绝策略(记录日志+降级处理) --- ### 二、线程可识别性问题 ```java Executors.defaultThreadFactory() // 默认线程工厂 ``` **问题分析**: - 生成的线程名格式为`pool-1-thread-N`,缺乏业务语义 - 生产环境排查问题时难以快速定位线程归属 **改进方案**: ```java // 自定义线程工厂示例 new ThreadFactoryBuilder().setNameFormat("task-pool-%d").build() ``` --- ### 三、核心线程回收问题 **问题分析**: - `ThreadPoolExecutor`默认**不会回收核心线程**(即使空闲),此处配置核心线程数5将常驻内存 - 若业务存在低峰期,可能导致资源浪费 **改进方案**: ```java // 显式允许核心线程超时回收 threadPoolExecutor.allowCoreThreadTimeOut(true); ``` --- ### 四、参数配置建议 1. **keepAliveTime合理性** 当前配置10分钟空闲回收(`TimeUnit.MINUTES`),需根据任务特征调整: - CPU密集型任务:建议缩短回收时间(如1-5分钟) - IO密集型任务:可适当延长 2. **最大线程数限制** 最大线程数10可能无法充分利用多核资源,建议参考公式: ```java int cpuCores = Runtime.getRuntime().availableCores(); int maxThreads = cpuCores * N // N根据任务类型调整(通常2~4) ``` --- ### 五、完整改进示例 ```java @Bean("taskFlowThreadPool") public ThreadPoolExecutor taskFlowThreadPool() { int corePoolSize = Runtime.getRuntime().availableCores(); ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, corePoolSize * 2, 2L, TimeUnit.MINUTES, new LinkedBlockingQueue<>(500), new ThreadFactoryBuilder().setNameFormat("task-pool-%d").build(), new CustomRejectedExecutionHandler() // 自定义拒绝策略 ); executor.allowCoreThreadTimeOut(true); return executor; } ``` --- ### 六、生产环境最佳实践 1. **监控埋点**:通过Spring Boot Actuator暴露线程池指标 2. **动态调参**:结合配置中心实现运行时参数调整 3. **优雅关闭**:注册Shutdown Hook确保线程池安全终止 4. **异常处理**:配合`ThreadPoolTaskExecutor`使用`@Async`时需配置异常处理器 > **最终建议**:线程池参数没有银弹配置,需要根据实际业务压力测试后确定,建议配合监控系统持续优化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值