浅记java线程池

发布时间:2026/7/14 5:35:56

浅记java线程池 Java 线程池一、为什么要用线程池不用线程池 new Thread(runnable).start() → 创建/销毁线程开销大 1000 个任务 1000 次创建 1000 次销毁 无法控制并发数 → 内存打爆 OOM 用线程池 线程复用任务排队资源可控 1000 个任务 最多 corePoolSize 个线程二、ThreadPoolExecutor 七大参数publicThreadPoolExecutor(intcorePoolSize,// 1. 核心线程数intmaximumPoolSize,// 2. 最大线程数longkeepAliveTime,// 3. 非核心线程空闲存活时间TimeUnitunit,// 4. 时间单位BlockingQueueRunnableworkQueue,// 5. 任务队列ThreadFactorythreadFactory,// 6. 线程工厂RejectedExecutionHandlerhandler// 7. 拒绝策略)2.1 逐参数详解corePoolSize — 核心线程数含义 - 线程池始终保留的线程数即使它们处于空闲状态 - 默认情况下核心线程不会被回收除非 allowCoreThreadTimeOut(true) 设置原则 CPU 密集型 N_CPU 1 IO 密集型 N_CPU × 2 混合型 N_CPU × 2 ~ 4压测决定 不推荐 Runtime.getRuntime().availableProcessors() × N ↑ 在容器/虚拟化环境中这个值可能不准maximumPoolSize — 最大线程数含义 - 线程池允许创建的最大线程数 - 只有在队列满了之后才会创建超过 corePoolSize 的线程 注意 maximumPoolSize 只在队列满了之后才发挥作用。 如果用的是无界队列如 LinkedBlockingQueue 不设容量 maximumPoolSize 参数根本不生效 —— 任务永远先进队列。keepAliveTime — 空闲存活时间含义 - 当线程数 corePoolSize 时多余的空闲线程等待新任务的最长时间 - 超时后非核心线程被销毁 特殊配置 executor.allowCoreThreadTimeOut(true) → keepAliveTime 对核心线程也生效 → 核心线程空闲超时也会被回收workQueue — 任务队列关键队列类型特性适用场景SynchronousQueue不存储任务来一个任务必须立刻有线程处理大量短任务任务数 线程数就拒LinkedBlockingQueue无界无限排队maximumPoolSize 不生效任务平稳不会暴增LinkedBlockingQueue有界有限排队配合 maxPoolSize 使用⭐推荐资源可控ArrayBlockingQueue有界数组实现固定容量任务数可预测PriorityBlockingQueue按优先级排序有优先级的任务DelayQueue延迟执行定时任务threadFactory — 线程工厂ThreadFactorynamedFactorynewThreadFactory(){privatefinalAtomicIntegercountnewAtomicInteger(1);OverridepublicThreadnewThread(Runnabler){ThreadtnewThread(r);t.setName(order-pool-count.getAndIncrement());// 有名字好排查t.setDaemon(false);// 非守护线程默认t.setUncaughtExceptionHandler((thread,e)-{log.error(线程 {} 异常退出,thread.getName(),e);});returnt;}};三、任务执行流程必须背熟execute(task) 被调用 │ ▼ ┌ 当前线程数 corePoolSize │ │ │ 是 → new 新线程执行任务即使有空闲线程 │ │ 否 → 任务入 workQueue │ │ │ ┌──入队成功 → 等待空闲线程消费 │ │ │ └──入队失败队列满 │ │ │ 当前线程数 maximumPoolSize │ │ │ 是 → new 新线程执行任务 │ 否 → 执行拒绝策略重要细节线程池创建线程的时机 第 1 ~ corePoolSize 个任务 → 立即创建线程 第 corePoolSize1 ~ 队列满 → 入队等待 队列满之后的任务 → 创建新线程最多到 maximumPoolSize 线程数到 maximumPoolSize 且队列满 → 拒绝四、拒绝策略拒绝时机 ① 线程数 maximumPoolSize 且 workQueue 满 ② 线程池已 shutdown还往里面提交任务四种内置策略策略行为适用AbortPolicy默认抛RejectedExecutionException必须感知到拒绝CallerRunsPolicy由提交任务的线程自己执行不想丢任务可以降速DiscardPolicy静默丢弃不报错任务允许丢失如非关键日志DiscardOldestPolicy丢弃队头最老的任务重试提交优先保证新任务// AbortPolicy — 默认抛异常ThreadPoolExecutorpoolnewThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,newLinkedBlockingQueue(100),newThreadPoolExecutor.AbortPolicy());// CallerRunsPolicy — 谁提交谁自己跑// 好处提交线程被占用来执行任务 → 提交速度自然降低// 适合不能丢任务的场景newThreadPoolExecutor.CallerRunsPolicy();// DiscardPolicy — 丢了就丢了日志都不会打newThreadPoolExecutor.DiscardPolicy();// DiscardOldestPolicy — 把队头最老的丢了塞进新任务newThreadPoolExecutor.DiscardOldestPolicy();自定义拒绝策略// 场景拒绝时记录日志 发告警 落库待后续重试publicclassAlertRejectedHandlerimplementsRejectedExecutionHandler{OverridepublicvoidrejectedExecution(Runnabler,ThreadPoolExecutorexecutor){log.error(线程池已满poolSize{}, queueSize{}, task{},executor.getPoolSize(),executor.getQueue().size(),r.toString());// 发钉钉/飞书告警alertService.send(线程池拒绝任务);// 落库后续定时任务拉取重试rejectedTaskRepository.save(r);}}五、预定义线程池 — 为什么都不推荐// Executors 提供的便捷方法 — 全都埋了坑// ❌ newFixedThreadPool — LinkedBlockingQueue 无界OOMExecutors.newFixedThreadPool(10);// ❌ newCachedThreadPool — SynchronousQueue Integer.MAX_VALUE 线程数// 来一个任务没线程就创建来 10W 个就创建 10W 个线程 → OOMExecutors.newCachedThreadPool();// ❌ newSingleThreadExecutor — 同样无界队列Executors.newSingleThreadExecutor();// ❌ newScheduledThreadPool — 最大线程数 Integer.MAX_VALUEExecutors.newScheduledThreadPool(4);// ✅ 正确做法全部自己 new ThreadPoolExecutor显式指定队列容量newThreadPoolExecutor(10,20,60L,TimeUnit.SECONDS,newLinkedBlockingQueue(1000),// ← 必须有界namedFactory,newCallerRunsPolicy());六、线程池状态机ThreadPoolExecutor 用一个 AtomicInteger ctl 同时表示 高 3 位线程池状态 低 29 位工作线程数量 RUNNING111 │ shutdown() ▼ SHUTDOWN000 │ 队列空 线程全部结束 ▼ TIDYING-1 │ terminated() 回调执行完 ▼ TERMINATED-3 RUNNING │ shutdownNow() ▼ STOP001 │ ▼ TIDYING → TERMINATED状态接受新任务处理队列中任务怎么进入RUNNING✅✅new ThreadPoolExecutorSHUTDOWN❌✅shutdown()STOP❌❌shutdownNow()TIDYING——过渡态TERMINATED——terminated() 执行完七、线程池监控ComponentpublicclassThreadPoolMonitor{AutowiredprivateThreadPoolExecutororderPool;Scheduled(fixedDelay5000)// 每 5 秒采集一次publicvoidmonitor(){intcoreSizeorderPool.getCorePoolSize();intmaxSizeorderPool.getMaximumPoolSize();intactiveCountorderPool.getActiveCount();intpoolSizeorderPool.getPoolSize();intqueueSizeorderPool.getQueue().size();longcompletedorderPool.getCompletedTaskCount();// 告警规则if(activeCountmaxSize){log.warn(线程池已满active{}, max{},activeCount,maxSize);}if(queueSize500){log.warn(队列堆积严重queueSize{},queueSize);}// 推送到 Prometheus / Grafana / 飞书metrics.push(thread_pool_active,activeCount);metrics.push(thread_pool_queue,queueSize);}}关键监控指标1. activeCount / maximumPoolSize → 线程池负载率 80% 告警 2. queueSize → 队列积压 容量 80% 告警 3. rejectedCount → 拒绝次数有任何拒绝立刻告警 4. completedTaskCount → 总完成量看吞吐趋势 5. poolSize → 当前线程数观察是否需要扩容八、线程池配置实战8.1 Spring Boot 配置ConfigurationpublicclassThreadPoolConfig{/** * 订单处理线程池 — IO 密集型 */Bean(orderExecutor)publicThreadPoolExecutororderExecutor(){returnnewThreadPoolExecutor(10,// 核心 1020,// 最大 20120,TimeUnit.SECONDS,// 空闲 2 分钟回收newLinkedBlockingQueue(500),// 队列 500newNamedThreadFactory(order),// 线程命名newThreadPoolExecutor.CallerRunsPolicy()// 拒绝时降速);}/** * 报表计算线程池 — CPU 密集型 */Bean(reportExecutor)publicThreadPoolExecutorreportExecutor(){returnnewThreadPoolExecutor(4,// CPU 核心数8,60,TimeUnit.SECONDS,newArrayBlockingQueue(100),newNamedThreadFactory(report),newThreadPoolExecutor.AbortPolicy()// 报表不能丢);}}8.2 动态调整// 运行时动态调整配 Apollo / Nacos 配置中心实时生效ComponentpublicclassDynamicThreadPool{Resource(nameorderExecutor)privateThreadPoolExecutororderPool;ApolloConfigChangeListenerpublicvoidonChange(ConfigChangeEventevent){if(event.isChanged(order.pool.core)){intnewCoreevent.getInt(order.pool.core,10);orderPool.setCorePoolSize(newCore);log.info(核心线程数调整: {} → {},orderPool.getCorePoolSize(),newCore);}if(event.isChanged(order.pool.max)){orderPool.setMaximumPoolSize(event.getInt(order.pool.max,20));}}}8.3 优雅关闭ComponentpublicclassGracefulShutdownimplementsDisposableBean{Resource(nameorderExecutor)privateThreadPoolExecutororderPool;Overridepublicvoiddestroy(){log.info(开始关闭线程池...);// 第一步停止接受新任务orderPool.shutdown();try{// 第二步等待已提交任务完成最多等 60 秒if(!orderPool.awaitTermination(60,TimeUnit.SECONDS)){// 第三步超时了就强制中断ListRunnableundoneorderPool.shutdownNow();log.warn(强制关闭未完成任务数: {},undone.size());// 第四步再等 10 秒if(!orderPool.awaitTermination(10,TimeUnit.SECONDS)){log.error(线程池拒绝关闭);}}}catch(InterruptedExceptione){orderPool.shutdownNow();Thread.currentThread().interrupt();}log.info(线程池已关闭);}}8.4 在异步任务中传递上下文// 场景异步任务需要用到当前请求的 tenant_id、user_id// 问题异步线程的 ThreadLocal 是空的// 解决用装饰器包装任务publicclassContextDecoratorimplementsTaskDecorator{OverridepublicRunnabledecorate(Runnablerunnable){// 从主线程捕获上下文StringtenantIdTenantContext.getTenantId();LonguserIdUserContext.getUserId();StringtraceIdMDC.get(traceId);return()-{try{// 设置到异步线程TenantContext.setTenantId(tenantId);UserContext.setUserId(userId);MDC.put(traceId,traceId);runnable.run();}finally{// 清理TenantContext.clear();UserContext.clear();MDC.clear();}};}}// 配置中设置executor.setTaskDecorator(newContextDecorator());九、ThreadPoolExecutor 与 ForkJoinPoolThreadPoolExecutorForkJoinPool设计目标独立任务并发执行可拆分的递归任务核心机制阻塞队列 线程池工作窃取Work Stealing适用Web 请求、RPC 调用并行计算、递归算法Java 默认—parallelStream() / CompletableFuture 默认// ForkJoinPool 的工作窃取原理//// Thread-1 队列: [T1, T2, T3, T4] ← 任务多// Thread-2 队列: [T5] ← 快做完了// Thread-2 从 Thread-1 的队尾偷一个 → 负载均衡//// 这就是为什么 CompletableFuture.supplyAsync() 默认比 newFixedThreadPool 快十、常见问题排查问题 1任务不执行也不报错 → 检查队列是不是无界任务全在队列里等线程数没超过 corePoolSize → 解决换成有界队列 问题 2线程数一直涨到 maxPoolSize → 检查队列是不是太小核心线程来不及消费 → 解决增大队列容量 或 增大 corePoolSize 问题 3OOM: unable to create new native thread → 检查是不是用了无界队列 maximumPoolSize 太大 → 解决减小 maximumPoolSize改用有界队列 问题 4线程池 shutdown 后线程不退出 → 检查队列里的任务是不是阻塞在 IO 上不响应中断 → 解决任务的 IO 操作设置超时时间 问题 5任务被吞掉日志没有任何记录 → 检查submit() 返回的 Future 是不是没调 get() → 解决用 execute() 替代 submit()或调 Future.get()十一、总结线程池配置口诀 核 心 数 — CPU 密集 1IO 密集 ×2 队 列 长 — 必须设上限防止 OOM 拒 绝 策 — 关键业务 Abort非关键 CallerRuns 命 名 线 程 — 出问题好排查 监 控 告 警 — 线程数、队列长度、拒绝次数 三指标 优 雅 关 闭 — shutdown → awaitTermination → shutdownNow

相关新闻