首页 / 资讯中心 / 文章详情

线程池原理与高并发系统优化实战

线程池原理与高并发系统优化实战 ★ FEATURED ARTICLE
1. 为什么线程池是高性能系统的基石第一次接触线程池是在2015年处理一个电商秒杀系统时。当时我们的服务器在活动开始5秒内就被打爆日志里满是RejectedExecutionException。这个惨痛教训让我明白不理解线程池原理就谈不上构建高并发系统。线程池本质上是一个生产者-消费者模型的实现。想象一下医院的分诊系统挂号处生产者不断接收病人护士站消费者根据医生数量线程数安排就诊。如果候诊区队列满了新病人就会被告知改天再来拒绝策略。这个类比几乎涵盖了线程池的所有核心概念。2. 线程池七大参数详解与实战配置2.1 核心线程数与最大线程数corePoolSize和maximumPoolSize的关系就像医院常驻医生和临时医生。我建议这样设置// 计算密集型任务 int core Runtime.getRuntime().availableProcessors() 1 int max core * 2 // IO密集型任务 int core Runtime.getRuntime().availableProcessors() * 2 int max core * (1 avg_wait_time/avg_compute_time)关键经验线上环境永远要设置maxcore否则突发流量时队列积压会导致OOM。我们曾因此损失过百万订单。2.2 队列选型的五个维度对比队列类型特点适用场景内存风险SynchronousQueue零容量直接传递高吞吐短任务低ArrayBlockingQueue固定大小数组流量平稳场景可控LinkedBlockingQueue无界队列默认不推荐生产环境使用极高PriorityBlockingQueue优先级队列有任务优先级区分高DelayedWorkQueue延迟执行定时任务场景中实测发现使用ArrayBlockingQueue时队列大小建议设置为corePoolSize的2-4倍。3. 拒绝策略的四种武器与自定义实践3.1 JDK内置策略对比AbortPolicy默认直接抛出RejectedExecutionExceptionCallerRunsPolicy让提交任务的线程自己执行DiscardPolicy静默丢弃新任务DiscardOldestPolicy丢弃队列最老的任务3.2 自定义拒绝策略实战我们在支付系统中实现了这样的策略new RejectedExecutionHandler() { Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { try { // 尝试等待1秒 e.getQueue().offer(r, 1000, TimeUnit.MILLISECONDS); } catch (InterruptedException ex) { // 记录到死信队列 deadLetterQueue.put(r); } } } }这个方案让我们的支付成功率在618大促期间提升了12%。4. 高并发场景下的线程池选型矩阵4.1 电商秒杀系统配置ThreadPoolExecutor seckillExecutor new ThreadPoolExecutor( 32, // core 256, // max 60, // keepAlive TimeUnit.SECONDS, new ArrayBlockingQueue(1000), new CustomRejectPolicy());关键技巧预热核心线程提前调用prestartAllCoreThreads()监控队列堆积当队列大小持续80%容量时触发扩容动态调整参数通过JMX实时修改corePoolSize4.2 微服务调用优化使用Guava的ListeningExecutorService包装线程池ListeningExecutorService executor MoreExecutors.listeningDecorator( Executors.newFixedThreadPool(16)); ListenableFutureResult future executor.submit(() - { // 服务调用 return feignClient.call(); }); Futures.addCallback(future, new FutureCallbackResult() { Override public void onSuccess(Result result) { // 处理成功逻辑 } Override public void onFailure(Throwable t) { // 熔断降级处理 } }, executor);5. 线程池监控与问题排查实战5.1 必须监控的六个指标活跃线程数executor.getActiveCount()队列剩余容量executor.getQueue().remainingCapacity()历史最大线程数需要自定义扩展拒绝次数统计自定义RejectedExecutionHandler任务平均耗时继承ThreadPoolExecutor重写afterExecute任务失败率同上5.2 典型问题排查案例现象CPU使用率100%但吞吐量下降 排查步骤jstack获取线程dump发现80%线程处于BLOCKED状态检查任务代码存在同步锁竞争解决方案减小线程数降低锁竞争改用ConcurrentHashMap替代同步块增加锁分段6. 进阶技巧线程池的优雅关闭大多数开发者忽略的正确关闭姿势executor.shutdown(); // 禁止新任务提交 try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 取消剩余任务 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { System.err.println(线程池未完全关闭); } } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); }特别提醒Spring的PreDestroy中必须处理双中断场景否则可能造成应用关闭卡死。
阅读完成 · 觉得有帮助?
咨询建站