【架构实战】线程池设计:高并发系统的资源管理艺术

一、一个newFixedThreadPool差点拖垮整个系统

2021年,我们一个新人写了一段代码:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(50);
for (Order order : orders) {
    executor.submit(() -> processOrder(order));
}

看起来没问题对吧?但他没考虑一点:orders有10万条,每条任务要处理3秒。

50个线程处理10万条,需要6000秒。更关键的是,每个任务还要往另一个队列里塞数据,那个队列也是无界的。

内存直接被撑爆,OOM崩溃。

从那以后,我们对线程池的使用有了严格的规范。


二、线程池核心参数

2.1 ThreadPoolExecutor七大参数

java 复制代码
/**
 * ThreadPoolExecutor七大参数
 */
public ThreadPoolExecutor(
    int corePoolSize,       // 核心线程数
    int maximumPoolSize,    // 最大线程数
    long keepAliveTime,     // 非核心线程空闲存活时间
    TimeUnit unit,          // 时间单位
    BlockingQueue<Runnable> workQueue,  // 工作队列
    ThreadFactory threadFactory,         // 线程工厂
    RejectedExecutionHandler handler     // 拒绝策略
)

2.2 参数配置原则

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                  线程池参数配置原则                                │
│                                                                  │
│  CPU密集型任务:                                                │
│  - 核心线程数 = CPU核数 + 1                                    │
│  - 最大线程数 = CPU核数 + 1                                    │
│  - 队列:有界队列                                               │
│                                                                  │
│  IO密集型任务:                                                 │
│  - 核心线程数 = CPU核数 × 2                                    │
│  - 最大线程数 = CPU核数 × 4                                    │
│  - 队列:有界队列                                               │
│                                                                  │
│  混合型任务:                                                   │
│  - 核心线程数 = CPU核数 × 2                                    │
│  - 最大线程数 = CPU核数 × 4~8                                  │
│  - 队列:有界队列                                               │
│                                                                  │
│  ⚠️ 关键原则:                                                  │
│  1. 必须使用有界队列,防止OOM                                   │
│  2. 必须设置合理的拒绝策略                                      │
│  3. 线程命名要可识别                                            │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

三、线程池配置最佳实践

3.1 基础配置

java 复制代码
/**
 * 线程池配置
 */
@Configuration
public class ThreadPoolConfig {
    
    @Bean("orderProcessPool")
    public ThreadPoolExecutor orderProcessPool() {
        return new ThreadPoolExecutor(
            10,                                     // 核心线程数
            50,                                     // 最大线程数
            60, TimeUnit.SECONDS,                   // 空闲存活时间
            new LinkedBlockingQueue<>(1000),         // 有界队列(容量1000)
            new NamedThreadFactory("order-process"), // 命名线程工厂
            new CustomRejectedExecutionHandler()     // 自定义拒绝策略
        );
    }
    
    @Bean("asyncTaskPool")
    public ThreadPoolExecutor asyncTaskPool() {
        return new ThreadPoolExecutor(
            5,
            20,
            60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(500),
            new NamedThreadFactory("async-task"),
            new ThreadPoolExecutor.CallerRunsPolicy()  // 调用者执行
        );
    }
}

/**
 * 命名线程工厂
 */
public class NamedThreadFactory implements ThreadFactory {
    
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    
    public NamedThreadFactory(String namePrefix) {
        this.namePrefix = namePrefix + "-thread-";
    }
    
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
        t.setDaemon(false);
        t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

3.2 自定义拒绝策略

java 复制代码
/**
 * 自定义拒绝策略:记录日志 + 降级处理
 */
@Slf4j
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
    
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        log.error("线程池拒绝执行: pool={}, active={}, queue={}, completed={}", 
            executor,
            executor.getActiveCount(),
            executor.getQueue().size(),
            executor.getCompletedTaskCount());
        
        // 降级方案1:尝试再次入队(等待1秒)
        try {
            if (!executor.getQueue().offer(r, 1, TimeUnit.SECONDS)) {
                log.error("再次入队失败,执行降级处理");
                // 降级方案2:记录到数据库,后续重试
                saveToRetryQueue(r);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("等待入队被中断");
        }
    }
    
    private void saveToRetryQueue(Runnable r) {
        // 将任务保存到数据库或消息队列,后续重试
    }
}

3.3 线程池监控

java 复制代码
/**
 * 线程池监控
 */
@Component
@Slf4j
public class ThreadPoolMonitor {
    
    @Autowired
    private Map<String, ThreadPoolExecutor> threadPools;
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    /**
     * 定时采集线程池指标
     */
    @Scheduled(fixedRate = 5000)
    public void monitor() {
        threadPools.forEach((name, pool) -> {
            // 核心指标
            gauge(name, "active", pool.getActiveCount());
            gauge(name, "pool_size", pool.getPoolSize());
            gauge(name, "queue_size", pool.getQueue().size());
            gauge(name, "completed", pool.getCompletedTaskCount());
            gauge(name, "task_count", pool.getTaskCount());
            gauge(name, "largest_pool_size", pool.getLargestPoolSize());
            
            // 告警
            if (pool.getQueue().size() > pool.getQueue().size() * 0.8) {
                log.warn("线程池队列使用率超过80%: name={}, queue={}/{}", 
                    name, pool.getQueue().size(), 
                    ((LinkedBlockingQueue<?>) pool.getQueue()).remainingCapacity() + pool.getQueue().size());
            }
            
            if (pool.getActiveCount() == pool.getMaximumPoolSize()) {
                log.warn("线程池已满: name={}, active={}/{}", 
                    name, pool.getActiveCount(), pool.getMaximumPoolSize());
            }
        });
    }
    
    private void gauge(String poolName, String metric, double value) {
        meterRegistry.gauge("thread.pool." + metric, 
            Tags.of("pool", poolName), value);
    }
}

四、Spring异步线程池

4.1 配置

java 复制代码
/**
 * Spring异步线程池配置
 */
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(500);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("异步任务执行失败: method={}, params={}", 
                method.getName(), Arrays.toString(params), ex);
        };
    }
}

/**
 * 异步任务使用
 */
@Service
@Slf4j
public class AsyncTaskService {
    
    /**
     * 异步发送邮件
     */
    @Async
    public CompletableFuture<Void> sendEmailAsync(String to, String subject, String content) {
        emailService.send(to, subject, content);
        return CompletableFuture.completedFuture(null);
    }
    
    /**
     * 异步处理订单
     */
    @Async("orderProcessPool")  // 指定线程池
    public CompletableFuture<OrderResult> processOrderAsync(Order order) {
        OrderResult result = processOrder(order);
        return CompletableFuture.completedFuture(result);
    }
}

五、踩坑实录

坑1:使用Executors创建线程池

newFixedThreadPoolnewSingleThreadPool使用无界队列,任务堆积导致OOM。

解决:永远不要用Executors创建线程池,用ThreadPoolExecutor。

坑2:队列太大

队列设了10000,任务堆积在队列里,响应时间越来越长。

解决:队列容量要适中,满了就创建新线程或拒绝。

坑3:拒绝策略选错

用了AbortPolicy,任务被拒绝后直接抛异常,导致业务中断。

解决:根据业务选择合适的拒绝策略,推荐CallerRunsPolicy或自定义。

坑4:线程池没有关闭

应用关闭时线程池还在运行,任务丢失。

解决 :设置waitForTasksToCompleteOnShutdown=true,优雅关闭。

坑5:ThreadLocal泄漏

线程池中的线程是复用的,ThreadLocal的值可能残留。

解决:任务执行前后清理ThreadLocal,或使用TransmittableThreadLocal。


六、总结

线程池使用规范:

原则 说明
不用Executors 手动创建ThreadPoolExecutor
有界队列 防止OOM
命名线程 便于排查问题
拒绝策略 根据业务选择
监控告警 及时发现问题
优雅关闭 防止任务丢失

最佳实践:

  1. 线程池参数可配置(通过配置中心)
  2. 每个业务使用独立的线程池
  3. 线程池要有监控和告警
  4. 压测验证线程池参数
  5. 定期Review线程池配置

血的教训:

线程池不是配好就完事了。它需要持续监控、持续调优。一个配置不当的线程池,比没有线程池更可怕。

思考题: 你的系统线程池是怎么配置的?有没有出过问题?


个人观点,仅供参考

相关推荐
一个骇客2 小时前
分布式批处理:当你的单机脚本跑了一天一夜还没出结果
分布式·架构
小蒋学算法2 小时前
redis分布式锁实现
数据库·redis·分布式
故渊at2 小时前
系列一:架构思想进阶 | 第1篇 Android 架构演进实录:从 MVC 的“万能类”到 MVVM 的数据驱动
android·架构·mvc
珠***格2 小时前
四可装置核心技术:高精度采集、边缘计算、协议自适应
大数据·人工智能·分布式·能源·边缘计算
qingy_20462 小时前
【架构师之路】绪论
微服务·云原生·架构
AI人工智能+电脑小能手3 小时前
【大白话说Java面试题 第97题】【Mysql篇】第27题:说说分库与分表的设计?
java·开发语言·数据库·分布式·mysql·算法
Quz3 小时前
Qt Quick 粒子系统(一):架构总览与四层模型
qt·架构·qml
故渊at3 小时前
系列二:MVVM 深度实战与项目重构 | 第4篇 MVVM 完整架构搭建:从零打造企业级框架(Base 封装、全局状态与生命周期铁律)
重构·架构
●VON3 小时前
AtomGit Flutter鸿蒙客户端:项目架构概览
flutter·华为·架构·harmonyos·鸿蒙