玩转多线程/配置化线程池,看这一篇就够了(从搭建到使用一条龙服务)

206 阅读9分钟

线程池的使用场景

在赛博朋克2077年的数据中心,线程池是连接人类与AI的神经接口——每根光纤(线程)都承载着数据洪流,而调度器就是那个冷静的中央AI,它必须决定:是让所有光纤同时全速运转(资源耗尽),还是像交响乐指挥家一样,让每个音符(任务)在正确的时间响起?

就像你家的快递柜,线程池就是那个能同时存放20个包裹的智能柜。当快递员(任务)不断投递时,你是选择让柜子爆满(线程阻塞),还是让取件人(CPU)分批取件(任务队列)?线程池的妙处,就在于它让'等待'变得优雅,而不是让人抓狂

线程池作为并发编程的核心组件,其应用场景广泛且具有显著性能优势,以下是典型使用场景及技术实现要点:

一、高并发任务处理

在Web服务、API网关等场景中,线程池可限制并发线程数,避免系统资源耗尽。例如处理HTTP请求时,通过固定核心线程数(如CPU核数×2)和合理队列容量(如LinkedBlockingQueue)实现请求缓冲‌1。

二、批量任务执行

  • 数据导入/导出‌:如Elasticsearch批量导入场景,通过线程池并行处理数据分片,显著提升吞吐量。需注意批量大小调优和ES集群负载监控‌2。
  • 多数据源聚合‌:从微服务或数据库并行采集数据后汇总,利用CountDownLatch同步结果‌。

三、异步非阻塞操作

  • IO密集型任务‌:邮件发送、日志记录、第三方API调用等场景,通过提交任务到线程池避免主线程阻塞。
  • GUI应用‌:后台任务(如文件下载)与界面渲染分离,提升用户体验。

四、定时与周期性任务

通过ScheduledThreadPool实现定时任务调度,如定时数据备份、心跳检测等‌。

五、资源受限系统

在嵌入式或内存敏感环境中,线程池可限制线程数量,减少内存和CPU消耗,确保系统稳定性‌。

六、AI与高性能计算

  • 并行推理‌:如OpenALPR通过线程池实现车牌识别的多核并行处理,任务队列采用生产者-消费者模型。
  • 任务调度优化‌:AI训练中结合GPU计算与线程池流水线设计,提升资源利用率‌。

七、特殊场景优化

  • Tomcat模式改造‌:Spring线程池可调整为核心线程→非核心线程→队列的优先级策略,适应高并发需求‌3。
  • TraceID传递‌:通过封装ThreadPoolExecutor或使用Spring的ThreadPoolTaskExecutor,确保日志链路追踪‌8。

线程池配置需结合任务类型(CPU/IO密集型)和硬件资源,通过压测确定最优参数‌49。

搭建(粘贴即用)

如果把程序比作一家公司,线程池就是那个永远在加班的HR部门——明明招聘需求已经堆成山(任务队列),却总被老板(CPU)要求'再优化一下资源利用率'。而作为程序员,我们既要让这个HR部门高效运转,又要避免它因为过劳罢工(线程耗尽),这中间的平衡艺术,就是今天要聊的线程池生存指南。

1.1 线程池配置中心

线程池的核心参数共有七个,它们共同决定了线程池的行为特性和资源管理方式。

核心线程数(corePoolSize) ‌ 定义了线程池中长期维持的线程数量,即使这些线程处于空闲状态,通常也不会被回收,从而保证了线程池的基本处理能力。

最大线程数(maximumPoolSize) ‌ 是线程池允许创建的最大线程数量上限,当任务量激增且工作队列已满时,线程池可以创建新线程直至达到此值。

线程存活时间(keepAliveTime) ‌ 和 ‌时间单位(unit) ‌ 共同规定了非核心线程空闲时的最长存活时间,超时后这些线程将被终止以回收系统资源。

工作队列(workQueue) ‌ 作为任务的缓冲区域,用于保存待执行的任务,其类型(如ArrayBlockingQueue、LinkedBlockingQueue)直接影响线程池的排队机制和任务调度策略。

线程工厂(threadFactory) ‌ 用于自定义线程的创建方式,开发者可以通过它来设置线程名称、优先级等属性。

拒绝策略(rejectedExecutionHandler) ‌ 在线程池和队列均已满时被触发,用于处理无法接纳的新任务,常见的策略有直接抛出异常(AbortPolicy)或由调用者线程直接执行任务(CallerRunsPolicy)45。

这些参数需要根据具体的应用场景进行合理配置,例如对于计算密集型任务,通常建议将核心线程数设置为CPU核心数加一3。

@EnableAsync
@Configuration
public class ThreadPoolConfiguration {


    @Primary
    @Bean(ThreadPoolConsts.THREAD_POOL_NAME_DEFAULT)
    public Executor taskExecutor() {
        return buildExecutorByConfig(ThreadPoolConsts.THREAD_POOL_NAME_DEFAULT, null, null);
    }


    private Executor buildExecutorByConfig(String configKey, Integer corePoolSize, Integer maxPoolSize) {
        ThreadPoolProperties config = new ThreadPoolProperties();
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        executor.setCorePoolSize(config.getCorePoolSize());
        executor.setMaxPoolSize(config.getMaxPoolSize());
        executor.setQueueCapacity(config.getQueueCapacity());
        executor.setThreadNamePrefix(configKey + "-");
        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
        executor.setWaitForTasksToCompleteOnShutdown(config.isWaitForTasksToCompleteOnShutdown());
        executor.setAwaitTerminationSeconds(config.getAwaitTerminationSeconds());
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        if (null != corePoolSize) {
            executor.setCorePoolSize(maxPoolSize);
        }
        if (null != corePoolSize) {
            executor.setMaxPoolSize(maxPoolSize);
        }
        executor.initialize();
        return executor;
    }

}

在程序的江湖里,线程池便是那'少林罗汉堂'——看似静默,实则暗藏三百六十五路精兵。当江湖突现紧急任务(高并发请求),方丈(调度器)轻敲木鱼,便有一队罗汉(线程)应声而出。但若任务如潮水般涌来,罗汉堂如何避免'人海战术'的消耗?且听我分解这'以少胜多'的线程池兵法

1.2 线程池名称

public class ThreadPoolConsts {

    private ThreadPoolConsts() {

    }

    /**
     * 公共线程池
     */
    public static final String THREAD_POOL_NAME_DEFAULT = "commonExecutor";

}

想象你是一位米其林主厨,线程池就是你的厨房团队——厨师(线程)们各司其职,切菜(任务处理)的、炒菜(计算)的、摆盘(IO等待)的。但突然涌入十桌订单(高并发),你是让所有厨师同时切十份洋葱(资源浪费),还是让一位厨师先切完再传菜(任务队列)?线程池的智慧,就在这'厨房调度'的学问里。

1.3 配置明细

@Component
@ConfigurationProperties(prefix = "xxx.thread-pool", ignoreUnknownFields = true)
public class ThreadPoolProperties {

    private int corePoolSize = 10;

    private int maxPoolSize = 20;

    private int queueCapacity = 500;

    private int keepAliveSeconds = 60;

    private boolean waitForTasksToCompleteOnShutdown = true;

    private int awaitTerminationSeconds = 60;

    public int getCorePoolSize() {
        return corePoolSize;
    }

    public void setCorePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
    }

    public int getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public int getQueueCapacity() {
        return queueCapacity;
    }

    public void setQueueCapacity(int queueCapacity) {
        this.queueCapacity = queueCapacity;
    }

    public int getKeepAliveSeconds() {
        return keepAliveSeconds;
    }

    public void setKeepAliveSeconds(int keepAliveSeconds) {
        this.keepAliveSeconds = keepAliveSeconds;
    }

    public boolean isWaitForTasksToCompleteOnShutdown() {
        return waitForTasksToCompleteOnShutdown;
    }

    public void setWaitForTasksToCompleteOnShutdown(boolean waitForTasksToCompleteOnShutdown) {
        this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown;
    }

    public int getAwaitTerminationSeconds() {
        return awaitTerminationSeconds;
    }

    public void setAwaitTerminationSeconds(int awaitTerminationSeconds) {
        this.awaitTerminationSeconds = awaitTerminationSeconds;
    }
}

1.4 可视化线程池任务执行器

public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

    @Override
    public void execute(Runnable task) {
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        showThreadPoolInfo();
        super.execute(new TaskWrapper(task, copyOfContextMap));
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        showThreadPoolInfo();
        super.execute(new TaskWrapper(task, copyOfContextMap), startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        showThreadPoolInfo();
        return super.submit(new TaskWrapper(task, copyOfContextMap));
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        showThreadPoolInfo();
        return super.submitListenable(new TaskWrapper(task, copyOfContextMap));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        showThreadPoolInfo();
        return super.submit(new FutureTaskWrapper<>(task, copyOfContextMap));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        showThreadPoolInfo();
        return super.submitListenable(new FutureTaskWrapper<>(task, copyOfContextMap));
    }

    /**
     * 日志打印
     */
    private void showThreadPoolInfo() {
        ThreadPoolExecutor threadPoolExecutor = this.getThreadPoolExecutor();
        LOGGER.info("ThreadPoolInfo, name: {}, activeCount: {}, taskCount: {}, completedTaskCount: {}",
                this.getThreadNamePrefix(), threadPoolExecutor.getActiveCount(), threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount());
    }

    public static class TaskWrapper implements Runnable {

        private final Runnable gift;

        private final Map<String, String> mdcContext;

        public TaskWrapper(Runnable target, Map<String, String> mdcContext) {
            this.gift = target;
            this.mdcContext = mdcContext;
        }

        @Override
        public void run() {
            if (this.gift == null) {
                return;
            }

            if (this.mdcContext != null) {
                MDC.setContextMap(mdcContext);
            }
            try {
                this.gift.run();
            } catch (Exception e) {
                LOGGER.error("TaskWrapper target execute exception.", e);
            } finally {
                MDC.clear();
            }
        }
    }

    public static class FutureTaskWrapper<T> implements Callable<T> {

        private final Callable<T> gift;

        private final Map<String, String> mdcContext;

        public FutureTaskWrapper(Callable<T> target, Map<String, String> mdcContext) {
            this.gift = target;
            this.mdcContext = mdcContext;
        }

        @Override
        public T call() throws Exception {
            if (this.gift == null) {
                return null;
            }
            if (this.mdcContext != null) {
                MDC.setContextMap(mdcContext);
            }
            try {
                return this.gift.call();
            } catch (Exception e) {
                LOGGER.error("FutureTaskWrapper target execute exception.", e);
            } finally {
                MDC.clear();
            }
            return null;
        }
    }
}

使用(一眼懂)

2.1 做异步或者离线任务 ...

@Resource(name = "commonExecutor")
private Executor executor;

private void useExecutor() {
    executor.execute(() -> {
        doYourWork();
    });
}

2.2 配合 CountDownLatch 使用

@Resource(name = "commonExecutor")
private Executor commonExecutor;


CountDownLatch cdl = new CountDownLatch(taskList.size());
taskList.forEach(task -> commonExecutor.execute(() -> {
    try {
       doSomeThing(task)
    } catch (Exception e) {
        LOGGER.error("写的破代码抛出来一个异常:", e);
    } finally {
        cdl.countDown();
    }
}));

try {
    cdl.await();
} catch (InterruptedException e) {
    LOGGER.error("写的破代码抛出来一个异常:", e);
    Thread.currentThread().interrupt();
}

2.3 配合 CompletableFuture 使用


@Resource(name = "commonExecutor")
private Executor commonExecutor;

private List<XXXResp> syncGetAllResult(List<XXXReq> reqList) {
    List<XXXResp> resultList = Lists.newArrayList();
    // 加入线程池异步处理
    List<CompletableFuture<Map<String, List<XXXResp>>>> futureList = reqList
            .stream().map(req -> CompletableFuture
                    .supplyAsync(() -> getCurrResult(req), commonExecutor)
                    .exceptionally(ex -> {
                        throw new ServiceException(String.format("初始化数据失败入参(%s),异常: %s", JSONUtil.toJsonStr(req), ex.getMessage()));
                    })
            ).collect(Collectors.toList());
    //获取所有执行结果
    CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
    //将所有结果汇总
    CompletableFuture<List<Map<String, List<XXXResp>>>> allContentsFuture = allFutures.thenApply(v ->
            futureList.stream().map(CompletableFuture::join).collect(Collectors.toList()));
    try {
        List<Map<String, List<XXXResp>>> list = allContentsFuture.get();
        list.forEach(m -> m.forEach((k, respList) -> resultList.addAll(respList)));
    } catch (Exception e) {
        LOGGER.error("获取结果异常:{}", e.getMessage());
        Thread.currentThread().interrupt();
    }
    return resultList;
}

工具类封装(并行任务)

public enum CompletableFutureSimpleThreadPool {

    /**
     * 单例对象
     */
    INSTANCE;

    @Resource(name = "commonExecutor")
    private Executor executor;

    private static final Logger log = LoggerFactory.getLogger(CompletableFutureSimpleThreadPool.class);


    /**
     * 执行多个任务(无返回值)
     *
     * @param tasks 任务数组
     */
    public static void executeTasks(@Nonnull Runnable... tasks) {
        if (ObjectUtil.isNull(tasks)) {
            log.error("[线程池][执行任务] 任务为空");
            return;
        }
        // 使用线程池并行执行多个任务
        CompletableFuture<?>[] futures = Arrays.stream(tasks)
                .map(task -> CompletableFuture.supplyAsync(() -> task, INSTANCE.executor)
                        .exceptionally(ex -> {
                            throw new BusinessException(String.format("执行任务异常: %s", ex));
                        })).toArray(CompletableFuture[]::new);

        // 等待所有任务执行完成
        CompletableFuture.allOf(futures).join();
    }

    /**
     * 执行多个任务(有返回值,使用 CompletableFuture 作为任务)
     *
     * @param tasks 任务数组
     * @return 任务执行结果的列表
     */
    @SafeVarargs
    public static <T> List<T> executeCompletableFutures(@Nonnull CompletableFuture<T>... tasks) {
        if (ObjectUtil.isNull(tasks)) {
            log.error("[线程池][执行任务] 任务为空");
            return Collections.emptyList();
        }
        // 使用 allOf 等待所有任务完成
        CompletableFuture<Void> allOf = CompletableFuture.allOf(tasks);
        allOf.join();

        // 收集任务结果
        return Arrays.stream(tasks)
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    /**
     * 执行多个任务(有返回值,使用 Supplier 作为任务)
     *
     * @param tasks 任务数组
     * @return 任务执行结果的列表
     */
    @SafeVarargs
    public static <T> List<T> executeSuppliers(@Nonnull Supplier<T>... tasks) {
        if (ObjectUtil.isNull(tasks)) {
            log.error("[线程池][执行任务] 任务为空");
            return Collections.emptyList();
        }


        // 执行多个任务并收集结果
        List<CompletableFuture<T>> futures = Arrays.stream(tasks)
                .map(task -> CompletableFuture.supplyAsync(() -> {
                    try {
                        return task.get();
                    } catch (Exception e) {
                        log.error("任务执行失败", e);
                        return null; // 或者可以选择抛出 RuntimeException
                    }
                }, INSTANCE.executor))
                .collect(Collectors.toList());

        // 获取所有任务的执行结果
        return futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }


}

所以,下次当你的程序因为线程池配置不当而卡成PPT时,不妨想想:这就像让一个HR部门同时处理100份简历,结果所有人都在吵架谁先看谁的。记住,线程池不是许愿池——扔多少任务都不会自动变快,但合理配置的线程池,绝对能让你的程序从'996ICU'变成'准点下班'的模范员工。

最后,送各位程序员一句厨房箴言:线程池不是让你把所有食材(任务)同时扔进锅(CPU),而是像米其林主厨一样,让每个步骤(线程)在正确的时间完成。毕竟,谁也不想吃一道'夹生饭'(任务未完成)的程序,对吧?