山西省网站建设_网站建设公司_建站流程_seo优化
2025/12/17 22:23:29 网站建设 项目流程

文章目录

  • 零、引入
  • 一、王二的新坑:只知用 Executors,不知 ThreadPoolExecutor
    • ➡️ ThreadPoolExecutor 的 “命脉”:7 个核心参数
  • 二、ThreadPoolExecutor 工作原理:流水线怎么处理零件?
    • 👉 工作流程
    • ✔️ 拒绝策略:料箱满了怎么办?(面试高频)
  • 三、实战:自定义 ThreadPoolExecutor,掌控一切
  • 四、面试高频题:ThreadPoolExecutor 核心问题(附答案)
    • ➡️ 面试题 1:ThreadPoolExecutor 的 corePoolSize 和 maximumPoolSize 有什么区别?怎么设置?
    • 📢 面试题 2:ThreadPoolExecutor 的任务队列有哪些选择?分别用在什么场景?
    • ✅ 面试题 3:为什么不推荐用 Executors 创建线程池?
  • 五、总结:ThreadPoolExecutor 核心心法(王二编的顺口溜)
    • ❓ 哇哥的血泪教训


📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌

📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌


零、引入

王二从面试间出来,脸比锅底还黑。面试官最后抛了个问题:“ThreadPoolExecutor 的核心参数有哪些?工作原理是什么?” 他支支吾吾,只说出个 “核心线程数”,后面的全卡壳了 —— 心仪的岗位就这么飞了。

回到工位,哇哥正对着一份线程池监控报表皱眉,见王二这模样,便知是怎么回事。“ThreadPoolExecutor 是 Executor 框架的骨头,面试必考,你连骨头都没啃动,怎么能过?” 哇哥把报表推到他面前,“今天用车间流水线的道理,把这东西讲透,下次再被问,你就把面试官说懵。”

点赞 + 关注,跟着哇哥和王二,吃透 ThreadPoolExecutor 的核心,并发面试的半壁江山就稳了!

一、王二的新坑:只知用 Executors,不知 ThreadPoolExecutor

王二之前用 Executor,全靠 Executors 工具类,比如Executors.newFixedThreadPool(10),从来没深究过背后的 ThreadPoolExecutor。面试官一追问 “FixedThreadPool 的核心参数怎么设置的”,他就露怯了。

“Executors 是给新手用的拐杖,” 哇哥嗤笑一声,“它帮你封装了 ThreadPoolExecutor 的参数,但生产环境里,这拐杖迟早把你绊倒。你得知道 ThreadPoolExecutor 的构造方法,那才是真东西。”

➡️ ThreadPoolExecutor 的 “命脉”:7 个核心参数

哇哥打开 JDK 源码,指着 ThreadPoolExecutor 的构造方法:

// ThreadPoolExecutor的核心构造方法publicThreadPoolExecutor(intcorePoolSize,// 核心线程数intmaximumPoolSize,// 最大线程数longkeepAliveTime,// 非核心线程空闲时间TimeUnitunit,// 空闲时间单位BlockingQueue<Runnable>workQueue,// 任务队列ThreadFactorythreadFactory,// 线程工厂RejectedExecutionHandlerhandler// 拒绝策略){// ... 初始化逻辑}

“这 7 个参数,就是车间的‘管理制度’,” 哇哥拿车间流水线类比,一下就把抽象参数讲活了:


王二恍然大悟:“原来 newFixedThreadPool (10),就是把 corePoolSize 和 maximumPoolSize 都设为 10,相当于车间全是正式工,没有临时工!”

“总算开窍了,” 哇哥点头,“Executors.newFixedThreadPool (n) 的底层,就是 new ThreadPoolExecutor (n, n, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>())——它帮你填了参数,但你得知道填的是什么。”

二、ThreadPoolExecutor 工作原理:流水线怎么处理零件?

“知道了参数,还得懂工作流程,” 哇哥拿一支笔当零件,在桌上演示,“当一个任务(零件)过来,流水线(线程池)是这么处理的:

  • 先看正式工(核心线程) 有没有空:有空就安排正式工干;
  • 正式工都忙了,就把零件放进料箱(任务队列);
  • 料箱也满了,就招临时工(非核心线程)来干;
  • 正式工 + 临时工都满了,料箱也满了,就执行拒绝策略(比如告诉送零件的‘别送了,放不下了’)。”

👉 工作流程

✔️ 拒绝策略:料箱满了怎么办?(面试高频)

哇哥强调:“拒绝策略是面试必问,就像料箱满了,车间总得有个说法。JDK 默认提供 4 种拒绝策略:”

  • AbortPolicy(默认):直接抛RejectedExecutionException异常,简单粗暴;
  • CallerRunsPolicy:让提交任务的线程自己执行(比如送零件的人自己动手干),缓解压力;
  • DiscardPolicy:悄悄丢弃任务,不抛异常(风险高,慎用);
  • DiscardOldestPolicy:丢弃队列里最老的任务,再把新任务加进去。

“生产环境里,别用默认的 AbortPolicy,”哇哥提醒,“比如秒杀场景,任务满了直接抛异常,用户体验太差,用 CallerRunsPolicy 或者自定义拒绝策略(比如记录日志,返回‘系统繁忙,请重试’)更友好。”

三、实战:自定义 ThreadPoolExecutor,掌控一切


王二照着哇哥的指导,写了个自定义线程池的代码,把 7 个参数都配置了一遍,还加了自定义拒绝策略:

packagecn.tcmeta.threadpoolexecutor;importjava.util.concurrent.*;/** * @author: laoren * @description: 自定义ThreadPoolExecutor,生产环境可用 * @version: 1.0.0 */publicclassCustomThreadSample{staticvoidmain(){// 1. 核心参数配置intcorePoolSize=5;// 正式工5人intmaximumPoolSize=10;// 最多10人(5正式+5临时)longkeepAliveTime=60;// 临时工空闲60秒解雇TimeUnitunit=TimeUnit.SECONDS;// 时间单位:秒// 2. 任务队列:容量100的阻塞队列BlockingQueue<Runnable>workQueue=newLinkedBlockingQueue<>(100);// 3. 线程工厂:给线程命名,便于排查问题ThreadFactorythreadFactory=newThreadFactory(){privateintcount=0;@OverridepublicThreadnewThread(Runnabler){Threadthread=newThread(r);thread.setName("order-thread-"+(++count));// 线程名:订单处理线程-1returnthread;}};// 4. 自定义拒绝策略:记录日志+返回友好提示RejectedExecutionHandlerrejectedHandler=(r,executor)->{// 记录任务拒绝日志System.out.println("任务"+r.toString()+"被拒绝,当前线程池状态:"+"核心线程数="+executor.getCorePoolSize()+",活跃线程数="+executor.getActiveCount()+",队列任务数="+executor.getQueue().size());// 实际项目中可以抛自定义异常,让上层返回“系统繁忙”thrownewRejectedExecutionException("系统繁忙,请稍后重试");};// 5. 创建自定义线程池try(ThreadPoolExecutorthreadPool=newThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,rejectedHandler);){// 6. 提交任务(模拟120个订单任务)for(inti=0;i<120;i++){intorderId=i;threadPool.submit(()->{try{TimeUnit.MILLISECONDS.sleep(100);// 模拟处理订单耗时System.out.println(Thread.currentThread().getName()+":处理订单"+orderId+"完成");}catch(InterruptedExceptione){Thread.currentThread().interrupt();}});}// 7. 关闭线程池threadPool.shutdown();}}}


王二看着清晰的线程名和拒绝日志,感慨道:“这下出了问题,我能直接定位到是‘订单处理线程’的问题,比之前的 Thread-1 好多了!”

“这就是自定义线程池的好处,” 哇哥说,“线程命名、拒绝策略、队列容量,全在你掌控之中,生产环境出问题也能快速排查。”

四、面试高频题:ThreadPoolExecutor 核心问题(附答案)


哇哥整理了 3 道面试必考题,王二抄在小本本上,背得滚瓜烂熟:

➡️ 面试题 1:ThreadPoolExecutor 的 corePoolSize 和 maximumPoolSize 有什么区别?怎么设置?

答案:

  • 区别:corePoolSize 是核心线程数(正式工),即使空闲也不销毁;maximumPoolSize 是核心线程 + 非核心线程的总数(正式工 + 临时工),非核心线程空闲到 keepAliveTime 会销毁。

  • 设置依据:

    • CPU 密集型任务(比如计算):核心线程数 = CPU 核心数 + 1,避免线程切换开销;
    • IO 密集型任务(比如调用接口、查数据库):核心线程数 = CPU 核心数 * 2+1,因为线程大部分时间在等 IO,多开线程能提高利用率。

📢 面试题 2:ThreadPoolExecutor 的任务队列有哪些选择?分别用在什么场景?

答案:
常用的阻塞队列有 3 种:

  • LinkedBlockingQueue(链表队列):无界队列(默认),适合任务量稳定的场景,但任务过多会导致 OOM;
  • ArrayBlockingQueue(数组队列):有界队列,适合控制任务数量的场景,配合拒绝策略使用,避免 OOM;
  • SynchronousQueue(同步队列):零容量队列,任务必须马上被线程处理,适合实时性要求高的场景(比如秒杀),对应 Executors.newCachedThreadPool ()。

✅ 面试题 3:为什么不推荐用 Executors 创建线程池?

Executors 封装的线程池有 3 个致命问题,生产环境慎用

  • newFixedThreadPool/newSingleThreadExecutor:用 LinkedBlockingQueue(无界队列),任务过多会导致 OOM;
  • newCachedThreadPool:maximumPoolSize是 Integer.MAX_VALUE,任务过多会创建大量线程,导致 OOM;
  • newScheduledThreadPool:核心线程数固定,任务队列无界,同样有 OOM 风险。

解决方案:
用 ThreadPoolExecutor 自定义线程池,手动设置核心参数、有界队列和拒绝策略。

五、总结:ThreadPoolExecutor 核心心法(王二编的顺口溜)

七参数是命脉,流水线来类比;
核心线程是正式工,最大线程含临时;
队列是料箱,满了招临时;
拒绝策略要选好,用户体验差不了;
Executors 是拐杖,自定义才是真章。

❓ 哇哥的血泪教训

“我刚工作时,用 Executors.newCachedThreadPool 处理日志,” 哇哥回忆道,“有次系统出 bug,日志量暴增,线程池创建了几千个线程,JVM 直接 OOM 挂了。后来换成自定义线程池,队列设为 1000,拒绝策略用 CallerRunsPolicy,就算日志再多,也不会把系统拖垮 —— 这都是血的教训。”

关注我,下一篇咱们扒一扒 Executor 框架的实战优化 —— 怎么用 Executor 处理批量任务?线程池怎么监控?生产环境里,怎么防止线程池 “雪崩”?让你不仅会用 Executor,还能用得稳、用得好,成为并发领域的老手!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询