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

一、一个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线程池配置

血的教训:

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

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


个人观点,仅供参考

相关推荐
胡萝卜术8 小时前
深度重构:Agent Skills——从 Prompt 工程到能力工程
javascript·架构·github
踏月的造梦星球8 小时前
DMDPC 学习:架构、部署、运维与调优
运维·数据库·学习·架构
韩楚风8 小时前
【参天引擎】事务生命周期 / MVCC / Undo / ACID / 分布式事务 功能域整体解析
数据库·分布式·mysql·架构·cantian
通问AI8 小时前
Apple Intelligence 国行备案深度技术解析:阿里千问如何被集成到苹果端侧AI架构
人工智能·架构
明航咨询—张老师9 小时前
软考软件评测师技术深度剖析:2026年考试改革后的知识体系与备考架构
大数据·架构
研究员子楚10 小时前
GEO行业发展标准体系白皮书V2.0-第10卷 · 全球篇:跨国标准协同与全球品牌语义治理框架
数据库·人工智能·microsoft·架构·geo
ACP广源盛1392462567312 小时前
GSV6155@ACP# 搭配 AI 服务器、AI PC 完整适配方案
大数据·服务器·人工智能·分布式·单片机·嵌入式硬件
维基框架14 小时前
维基框架(Wiki-Framework) 1.2.0:Mybatis 升级为主从读写分离
java·后端·架构
heimeiyingwang14 小时前
【架构实战】SQL注入与XSS防御:常见Web漏洞的系统性修复
前端·sql·架构
在水一缸14 小时前
深度解析 GPT-5.6:大模型架构演进与复杂任务实战指南
大数据·人工智能·gpt·架构·大模型·架构演进·gpt-5.6