自定义线程池的使用

现在有一个使用多线程进行调优的案例:模拟使用多线程并发处理数据。

1.自定义一个线程池

//自定义一个线程池
class ThreadPoolManger {
    private final ThreadPoolExecutor executor;
    private final AtomicBoolean isShutdown;

    /**
     * 构造方法,用于初始化线程池。
     *
     * @param corePoolSize    核心线程池的大小
     * @param maximumPoolSize 最大线程池的大小
     * @param keepAliveTime   非核心线程空闲存活时间
     * @param unit            存活时间单位
     * @param queueCapacity   任务队列容量
     */
    public ThreadPoolManger(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                            TimeUnit unit, int queueCapacity) {
        //防止参数为负数
        if (corePoolSize <= 0 || maximumPoolSize <= 0 || queueCapacity < 0) {
            throw new IllegalArgumentException("Pool size and capacity must be positive.");
        }

        executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                new LinkedBlockingQueue<>(queueCapacity)
        );
        //初始线程池关闭状态为false
        this.isShutdown = new AtomicBoolean(false);
        configThreadPool();
    }

    /**
     * 配置线程池的线程工厂和拒绝策略。
     */
    private void configThreadPool() {
        //设置自定义的线程工厂
        executor.setThreadFactory(new ThreadFactory() {
            @Override
            //这个参数r,是该线程要执行的任务
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("CustomThreadPool" + thread.getId());
                thread.setDaemon(false);//是否设置为守护线程
                thread.setPriority(Thread.NORM_PRIORITY);
                return thread;
            }
        });
        //自定义拒绝策略
        executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
            @Override
            /**
             * Runnable r:被拒绝的任务。
             * ThreadPoolExecutor executor:调用执行任务的线程池。
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.err.println("任务 " + r.toString() + " 被拒绝, 线程池 " + executor.toString() + " 已满");
            }
        });
    }

    //把任务提交给线程池
    public void submit(Runnable task) {
        if (isShutdown.get()) {
            throw new IllegalStateException("ThreadPool is shut down");
        }
        executor.execute(task); // 使用execute方法提交任务
    }

    //关闭线程池
    public void shutdown() {
        //CAS操作确保线程池只被关闭一次
        //如果false,说明有其他线程竞争关闭了
        if (isShutdown.compareAndSet(false, true)) {
            executor.shutdown();
            try {
                // 等待线程池关闭,这段时间留给其他线程执行完任务,最多等待30秒
                if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                    // 如果超时,则尝试立即关闭
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                // 如果等待时被中断,重新设置线程中断标志,并尝试立即关闭线程池
                Thread.currentThread().interrupt();
                executor.shutdownNow();
            }
        }
    }

}

自定义线程池包含7个参数,这里我们全部自定义。

2.使用线程池并发处理数据

然后在main方法中,使用线程池中的线程,达到目的效果。

class ThreadDemo {

    //模拟项目中多线程的使用案例:并发处理数据
    public static void main(String[] args) {

        //假设这个集合就是我们要处理的数据集合,假设有10000条数据
        // 假设这是我们要处理的10000条数据
        List<Integer> data = new ArrayList<>();
        for (int i = 1; i <= 10000; i++) {
            data.add(i);
        }

        ThreadPoolManger threadPool = new ThreadPoolManger(
                5,10,20,TimeUnit.SECONDS,10
        );

        // 计算每个线程处理的数据量
        int totalData = data.size();
        int chunkSize = totalData / 10; // 每个线程处理1000条数据

        // 提交任务
        for (int i = 0; i < 10; i++) {
            int start = i * chunkSize;
            int end = start + chunkSize;

            // 确保最后一个线程处理所有剩余数据
            if (i == 9) {
                end = totalData; // 处理最后一部分数据
            }

            int finalEnd = end;
            threadPool.submit(new Runnable() {
                List<Integer> subList = data.subList(start, finalEnd);
                @Override
                public void run() {
                    for (Integer integer : subList) {
                        System.out.println("处理数据:"+integer);

                    }
                }
            });
        }
        // 关闭线程池
        threadPool.shutdown();
    }
}

3.碰到的问题

3.1

注意,这里我说两个我当时觉得奇怪的点:

两个问题:第一个,为什么这个end报错,start不报错

第二个问题:为什么这样把end复制一份,也没有加final修饰,finalEnd就不报错了

gpt的回答:

第一个问题:为什么第一段代码中 end 报错,而 start 不报错?

在Java中,匿名内部类(如 Runnable)可以访问外部类的变量,但这些变量必须是final或者是effectively final(即在赋值后没有被修改)。在第一段代码中,end 是一个循环变量,在每次循环迭代中都会被重新赋值。因此,当匿名内部类尝试使用 end 时,编译器无法确认 end 是 final 或 effectively final,所以会报错。

start 由于是局部变量,且在每次迭代中重新赋值,所以它也不能被访问,实际上它也会报错。但这里你可能没有注意到它的作用域问题,两个变量都是存在类似的问题。

第二个问题:为什么第二段代码中这样把 end 复制一份,也没有加 final 修饰,finalEnd 就不报错了?

在第二段代码中,finalEnd 是在 end 的基础上创建的一个新的局部变量。Java 的编译器能够确定 finalEnd 的值在定义后不会被修改,因此它被视为 effectively final。

当你将 end 的值赋给 finalEnd 时,finalEnd 不再依赖于外部变量 end 的生命周期。由于 finalEnd 的值在定义后不再改变,所以它符合可以在匿名内部类中使用的条件。

总结

  1. 第一个问题end 在循环中会被重新赋值,导致编译器认为它不是 final 或 effectively final,因此报错。
  2. 第二个问题finalEnd 是新的局部变量,且在赋值后不再修改,所以它符合匿名内部类的使用条件。

注意:如果把这段代码删掉,end变量没有再次赋值,就不会报错。

            if (i == 9) {
                end = totalData; // 处理最后一部分数据
            }

3.2

问题2,volatile关键字修饰的变量

volatile static int count = 0;

Java 中,count++ 这个操作实际上是由多个步骤组成的:读取当前值、加一、再写回。这些步骤在多线程环境中是非原子的,意味着两个线程可能会同时读取到相同的值,然后都加一并写回,最终只会增加一次计数,导致丢失更新。


但是,每个线程对于自己要处理的subList集合是不会出现数据丢失。因为每个线程都是处理自己的一段subList,没有像这个count一样,出现多线程竞争同一个变量的情况。

<think>嗯,用户问的是如何在Spring的@Scheduled定时任务中使用自定义线程池。我记得Spring Boot默认的定时任务是用单线程执行的,所以如果有多个定时任务的话,可能会有阻塞的问题。用户可能遇到了定时任务执行时间太长,或者任务之间互相影响的情况,所以想用自定义线程池来优化。 首先,我需要回顾一下相关知识。根据引用[4],提到使用@Async可以让定时任务异步执行,这样每个任务都会用不同的线程,避免阻塞。但用户的问题不是异步执行,而是配置自定义线程池给@Scheduled使用。那可能需要通过配置TaskScheduler来替换默认的线程池。 然后,引用[3]和[2]都提到了自定义线程池的重要性,特别是阿里规范推荐使用ThreadPoolExecutor而不是Executors。所以用户可能希望遵循最佳实践,配置自己的线程池参数,比如核心线程数、队列容量等。 接下来,我需要确定具体的配置步骤。根据之前的资料,可能需要创建一个配置类,实现SchedulingConfigurer接口,并重写configureTasks方法,设置自定义的TaskScheduler。这样就能覆盖默认的单线程调度器。 另外,引用[4]还提到,如果定时任务方法是异步的,可以使用@Async注解,并结合自定义线程池。这时候可能需要同时配置@EnableAsync和定义一个TaskExecutor的Bean。不过用户的问题更偏向于直接在@Scheduled中使用自定义线程池,而不是通过异步的方式。 所以步骤可能是这样的: 1. 添加@EnableScheduling注解启用定时任务。 2. 创建一个配置类实现SchedulingConfigurer接口。 3. 在该配置类中定义线程池的参数,比如核心线程数、线程名前缀等。 4. 在configureTasks方法中设置TaskScheduler,使用自定义线程池。 然后给出代码示例,展示如何配置线程池,并演示定时任务的使用。同时需要提醒用户注意任务之间的隔离和异常处理。 可能还需要比较一下使用自定义线程池和默认单线程的区别,以及使用@Async的方式有什么不同。比如,自定义线程池可以让所有定时任务共享这个池,而@Async可能需要每个任务单独配置,或者结合@Async的线程池使用。 另外,用户可能需要知道如何监控线程池的状态,或者如何调整参数来优化性能。不过问题主要是配置方法,所以示例代码应该足够。不过最好在相关问题里提到性能分析和优化方法,比如如何确定线程池的大小,如何处理任务堆积等。</think>### 如何在Scheduled定时任务中配置自定义线程池 #### 一、默认线程池的问题 Spring Boot默认使用单线程执行所有@Scheduled定时任务,当存在多个任务时会产生排队等待现象,可能导致任务执行延迟[^4]。 #### 二、配置自定义线程池 ##### 1. 实现`SchedulingConfigurer`接口 通过覆盖`configureTasks`方法指定线程池: ```java @Configuration @EnableScheduling public class SchedulerConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setPoolSize(5); // 设置核心线程数 taskScheduler.setThreadNamePrefix("custom-scheduler-"); taskScheduler.initialize(); taskRegistrar.setTaskScheduler(taskScheduler); } } ``` ##### 2. 使用`ThreadPoolExecutor`显式创建 符合阿里开发规范的自定义方式[^3]: ```java @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); scheduler.setWaitForTasksToCompleteOnShutdown(true); return scheduler; } ``` #### 三、定时任务示例 ```java @Service public class ScheduledService { // 每5秒执行,使用自定义线程池 @Scheduled(fixedRate = 5000) public void task1() { System.out.println(Thread.currentThread().getName() + "执行任务1"); } // 每天10点执行 @Scheduled(cron = "0 0 10 * * ?") public void task2() { System.out.println(Thread.currentThread().getName() + "执行任务2"); } } ``` #### 四、关键配置参数 | 参数名称 | 作用说明 | 推荐值 | |----------------------|-------------------------------|----------| | poolSize | 核心线程数 | 根据CPU核心数 | | threadNamePrefix | 线程名前缀 | 业务相关标识符 | | awaitTerminationSeconds | 停机等待任务完成时间(秒) | 60 | | waitForTasksToCompleteOnShutdown | 是否等待任务完成 | true | #### 五、注意事项 1. 定时任务方法应做好异常处理,避免线程泄漏 2. IO密集型任务建议增大队列容量 3. 使用`@Async`可实现更细粒度的异步控制[^4] 4. 监控线程池状态:通过`ThreadPoolTaskScheduler.getScheduledThreadPoolExecutor()`获取运行时数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值