
你好欢迎来到我的博客我是【菜鸟不学编程】我是一个正在奋斗中的职场码农步入职场多年正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上我决定记录下自己的学习与成长过程也希望通过博客结识更多志同道合的朋友。️ 主要方向包括 Java 基础、Spring 全家桶、数据库优化、项目实战等也会分享一些踩坑经历与面试复盘希望能为还在迷茫中的你提供一些参考。 我相信写作是一种思考的过程分享是一种进步的方式。如果你和我一样热爱技术、热爱成长欢迎关注我一起交流进步全文目录前言线程池不是“省事”它是“权力”——你得会管 I. Executors 工厂newFixedThreadPool 和 newCachedThreadPool1newFixedThreadPool固定线程数 无界队列要小心2newCachedThreadPool无限扩容线程更刺激3工程上更推荐直接 new ThreadPoolExecutor可控II. ThreadFactory自定义线程创建别让排查像捉迷藏1一个实用的 ThreadFactory命名 异常兜底III. RejectedExecutionHandler饱和策略线程池“满了”怎么办1常见拒绝策略性格各不相同2自定义拒绝策略把“丢弃”变成“可观测”IV. 监控线程池getPoolSize 和 getActiveCount别当盲人开车1最简单的监控打印demo 够用了V. 关闭线程池shutdown 和 awaitTermination优雅停机别装死1正确姿势先温柔再强硬VI. 应用批量任务处理的框架一个能用的雏形1核心类BatchExecutor线程池 提交 统计2演示批量处理任务含监控 饱和策略3这个“框架雏形”解决了什么痛点结尾线程池最可怕的不是慢是“你不知道它为什么慢” 写在最后前言线程池不是“省事”它是“权力”——你得会管 线程池解决的是两件大事复用线程避免频繁创建/销毁线程的成本控制并发让系统在压力下“退一步”而不是直接爆炸但很多代码写成这样ExecutorServicepoolExecutors.newFixedThreadPool(200);然后一脸轻松“200 个线程够猛了吧”哥们儿线程不是越多越好。线程多了上下文切换、内存栈、锁竞争、调度抖动都会跟你贴贴。最后你得到的不是性能而是“热闹”。I. Executors 工厂newFixedThreadPool 和 newCachedThreadPool1newFixedThreadPool固定线程数 无界队列要小心newFixedThreadPool(n)的直觉是“最多 n 个线程并发执行”这没错。但它背后常见陷阱是默认使用无界队列任务堆积时可能把内存顶爆。适用场景更稳的任务量稳定、可预期任务执行时间差不多你能接受排队但要监控队列长度风险别装看不见上游突发流量导致任务不断入队 → 队列无限长 → OOM 或长尾延迟爆炸2newCachedThreadPool无限扩容线程更刺激newCachedThreadPool()的特点是没有核心线程限制实际可疯狂创建新线程适合大量短任务、突发场景理论上但它的风险也很“直白且吓人”任务一多且阻塞IO/锁等待 → 线程数暴涨 → 系统被拖死适用场景很多极短且非阻塞任务有强约束的上游限流否则容易失控我个人的态度很中立cached不是不能用但它属于“跑车模式”你得有刹车限流/超时/隔离。不然你一脚油门下去服务端直接给你表演原地解体。3工程上更推荐直接 new ThreadPoolExecutor可控与其赌默认实现不如把关键参数摆明corePoolSize / maximumPoolSizeworkQueue是否有界ThreadFactory线程命名、异常处理RejectedExecutionHandler饱和策略这一套才是“可运营”的线程池。II. ThreadFactory自定义线程创建别让排查像捉迷藏默认线程名长这样pool-1-thread-7。线上出问题时你看日志“哪个 pool-1是你吗 pool-1”你会很想骂人。1一个实用的 ThreadFactory命名 异常兜底importjava.util.concurrent.ThreadFactory;importjava.util.concurrent.atomic.AtomicInteger;publicclassNamedThreadFactoryimplementsThreadFactory{privatefinalStringprefix;privatefinalAtomicIntegeridxnewAtomicInteger(1);privatefinalbooleandaemon;publicNamedThreadFactory(Stringprefix,booleandaemon){this.prefixprefix;this.daemondaemon;}OverridepublicThreadnewThread(Runnabler){ThreadtnewThread(r);t.setName(prefix-idx.getAndIncrement());t.setDaemon(daemon);// 兜底别让线程悄悄挂了你还以为一切正常t.setUncaughtExceptionHandler((th,ex)-{System.err.println([Uncaught] th.getName() - ex);ex.printStackTrace();});returnt;}}有了它你至少能做到看日志就知道是哪个业务池线程异常不会“默默死去”排查速度直接起飞真的III. RejectedExecutionHandler饱和策略线程池“满了”怎么办当线程池达到最大线程数、队列也满了再提交任务就会触发拒绝策略。这块很多人不重视直到线上突然出现RejectedExecutionException然后你开始找人背锅。1常见拒绝策略性格各不相同AbortPolicy直接抛异常默认优点问题暴露快缺点上游没处理就炸CallerRunsPolicy让提交任务的线程自己执行优点天然背压提交者忙起来就提交慢了缺点可能拖慢调用线程比如拖慢 HTTP 线程DiscardPolicy直接丢弃任务优点不阻塞缺点丢了你还不一定知道很阴DiscardOldestPolicy丢最老的队列任务尝试塞新任务适用你更在意“最新任务”比如某些刷新类业务2自定义拒绝策略把“丢弃”变成“可观测”importjava.util.concurrent.RejectedExecutionHandler;importjava.util.concurrent.ThreadPoolExecutor;publicclassLogRejectedHandlerimplementsRejectedExecutionHandler{OverridepublicvoidrejectedExecution(Runnabler,ThreadPoolExecutorexecutor){System.err.println([Reject] poolexecutor, taskr);// 你可以在这里打点/报警/写入降级队列thrownewRuntimeException(Task rejected);}}真心建议你可以选择丢弃但你不能悄悄丢弃。悄悄丢弃你未来凌晨三点的噩梦。IV. 监控线程池getPoolSize 和 getActiveCount别当盲人开车线程池不是创建完就完事了它得“被观察”。你至少要知道当前线程数poolSize活跃线程数activeCount队列长度queue.size已完成任务数completedTaskCount1最简单的监控打印demo 够用了importjava.util.concurrent.ThreadPoolExecutor;publicclassPoolMonitor{publicstaticvoidprint(ThreadPoolExecutorex,Stringname){System.out.printf([%s] pool%d, active%d, queued%d, completed%d%n,name,ex.getPoolSize(),ex.getActiveCount(),ex.getQueue().size(),ex.getCompletedTaskCount());}}现实里你会接 Prometheus/Micrometer。但哪怕你只是在日志里每 5 秒打印一次也比“完全不知道发生了啥”强一万倍。V. 关闭线程池shutdown 和 awaitTermination优雅停机别装死停机这块很容易写成“摆烂式关机”pool.shutdownNow();然后线上任务一半没处理完、文件写一半、消息没 ack结果就是一地鸡毛。1正确姿势先温柔再强硬importjava.util.concurrent.ExecutorService;importjava.util.concurrent.TimeUnit;publicclassShutdowns{publicstaticvoidgracefulShutdown(ExecutorServicepool,longtimeout,TimeUnitunit){pool.shutdown();// 1) 不再接收新任务继续跑完队列try{if(!pool.awaitTermination(timeout,unit)){pool.shutdownNow();// 2) 超时还没停再强制中断if(!pool.awaitTermination(timeout,unit)){System.err.println(线程池仍未停止可能存在卡死任务);}}}catch(InterruptedExceptione){pool.shutdownNow();Thread.currentThread().interrupt();}}}核心思想大部分任务应该“正常跑完”实在跑不完也要能“可控退出”卡死要能被看见日志/告警VI. 应用批量任务处理的框架一个能用的雏形下面我们做一个“小框架”解决常见需求批量提交任务比如处理一堆文件/订单/图片限制队列有界避免内存爆可选背压CallerRuns任务结果收集成功/失败统计运行过程监控池状态、进度支持优雅停机你可以把它当成项目里的“批处理骨架”后面接 MQ、接数据库、接业务逻辑都行。1核心类BatchExecutor线程池 提交 统计importjava.time.Duration;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.*;importjava.util.concurrent.atomic.AtomicInteger;publicclassBatchExecutorimplementsAutoCloseable{privatefinalThreadPoolExecutorexecutor;publicBatchExecutor(intcore,intmax,intqueueCapacity,ThreadFactorythreadFactory,RejectedExecutionHandlerrejectionHandler){this.executornewThreadPoolExecutor(core,max,60,TimeUnit.SECONDS,newArrayBlockingQueue(queueCapacity),threadFactory,rejectionHandler);// 可选允许核心线程超时回收看业务this.executor.allowCoreThreadTimeOut(false);}publicTBatchResultTinvokeAll(ListCallableTtasks,Durationtimeout)throwsInterruptedException{longdeadlineNanosSystem.nanoTime()timeout.toNanos();ListFutureTfuturesnewArrayList(tasks.size());for(CallableTtask:tasks){futures.add(executor.submit(task));}BatchResultTresultnewBatchResult();for(FutureTf:futures){longleftdeadlineNanos-System.nanoTime();if(left0){result.timeouts.incrementAndGet();f.cancel(true);continue;}try{Tvf.get(left,TimeUnit.NANOSECONDS);result.success.add(v);}catch(TimeoutExceptione){result.timeouts.incrementAndGet();f.cancel(true);}catch(ExecutionExceptione){result.failures.add(e.getCause());}catch(CancellationExceptione){result.cancellations.incrementAndGet();}}returnresult;}publicThreadPoolExecutorraw(){returnexecutor;}Overridepublicvoidclose(){Shutdowns.gracefulShutdown(executor,30,TimeUnit.SECONDS);}publicstaticclassBatchResultT{publicfinalListTsuccessnewArrayList();publicfinalListThrowablefailuresnewArrayList();publicfinalAtomicIntegertimeoutsnewAtomicInteger(0);publicfinalAtomicIntegercancellationsnewAtomicInteger(0);publicStringsummary(){returnsuccesssuccess.size(), failuresfailures.size(), timeoutstimeouts.get(), cancellationscancellations.get();}}}2演示批量处理任务含监控 饱和策略我们模拟一批任务有的快、有的慢、有的故意失败。看它如何统计与监控。importjava.time.Duration;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.*;publicclassBatchDemo{publicstaticvoidmain(String[]args)throwsException{varfactorynewNamedThreadFactory(batch-worker,false);// 背压策略队列满了就让提交线程自己跑别让任务无限堆积varrejectnewThreadPoolExecutor.CallerRunsPolicy();try(BatchExecutorbenewBatchExecutor(4,// core8,// max50,// queueCapacity有界队列factory,reject)){// 启动一个简单监控线程demo 用生产建议接监控系统ScheduledExecutorServicemonitorExecutors.newSingleThreadScheduledExecutor(newNamedThreadFactory(pool-monitor,true));monitor.scheduleAtFixedRate(()-PoolMonitor.print(be.raw(),batch),0,1,TimeUnit.SECONDS);ListCallableStringtasksnewArrayList();for(inti1;i120;i){intidi;tasks.add(()-{// 模拟任务耗时if(id%170)thrownewRuntimeException(任务故意失败: id);if(id%100)Thread.sleep(800);// 慢任务elseThread.sleep(80);// 快任务returnOK-idThread.currentThread().getName();});}varresultbe.invokeAll(tasks,Duration.ofSeconds(10));System.out.println(Batch Done - result.summary());// 关闭监控monitor.shutdownNow();}}}3这个“框架雏形”解决了什么痛点队列有界避免任务无限堆积导致内存爆CallerRuns 背压池满了就让提交者“自己忙起来”从而自然减速结果可收集成功/失败/超时一目了然可监控你能看到活跃线程、队列长度、完成数可优雅停机不会一刀切shutdownNow()砍掉半截任务如果你把它接到真实业务里文件处理/订单对账/批量推送你会发现这套骨架非常“耐用”。结尾线程池最可怕的不是慢是“你不知道它为什么慢” 很多线上性能事故本质不是 CPU 不够、机器不行而是任务堆积没人看队列无界没人管拒绝策略默认抛异常没人兜线程命名乱七八糟没人能定位停机直接shutdownNow()任务丢了还以为自己很潇洒所以我最后再反问一句挺扎心但很实用**你的线程池是在替你“控流”还是在替你“藏雷”** 写在最后如果你觉得这篇文章对你有帮助或者有任何想法、建议欢迎在评论区留言交流你的每一个点赞 、收藏 ⭐、关注 ❤️都是我持续更新的最大动力我是一个在代码世界里不断摸索的小码农愿我们都能在成长的路上越走越远越学越强感谢你的阅读我们下篇文章再见✍️ 作者某个被流“治愈”过的 Java 老兵 日期2026-01-07 本文原创转载请注明出处。