Java并发与面试-每日必看(12)

前言

Java不秃,面试不慌!
欢迎来到这片 Java修炼场!这里没有枯燥的教科书,只有每日一更的 硬核知识+幽默吐槽,让你在欢笑中掌握 Java基础、算法、面试套路,摆脱“写代码如写诗、看代码如看天书”的困境。


线程池的工作原理

  想象一下,你家开了家网红奶茶店,店名就叫 “线程池奶茶铺”。这个奶茶店可是智能化运营的,雇了几个灵活的员工(线程),还配了个点单小助手(任务队列),再加上你这位店长(线程池管理者)掌控全局。

让我们看看这家奶茶店是怎么应对高峰期和淡季的吧:

1. 先雇几个基础员工(corePoolSize)

奶茶店刚开业时,老板(你)决定至少雇 3 个固定员工(corePoolSize),哪怕没人来买奶茶,他们也得在店里坐着,保证“来了就能干活”。

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    3, // corePoolSize
    5, // maximumPoolSize
    10, // keepAliveTime
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(2)
);

🧋 2. 订单多了,先排队(任务队列 workQueue)

生意开始火了起来,顾客络绎不绝。3 个员工忙不过来了,但好在你有个点单小助手,把新来的订单先记在本子上(任务队列)。

  • 翻译过来就是:
    只要本子没写满(workQueue未满),就不用急着招新人,员工们可以按顺序完成订单。

比喻:

顾客来了说:“我要一杯珍珠奶茶!” 小助手说:“好勒!先记下来了,等忙完就做!”


🍹 3. 本子写满了,赶紧找临时工(maximumPoolSize)

突然,一个网红博主推荐了你家店,顾客排起长队。本子也记满了(队列满),你这才慌了,赶紧打电话给兼职群:“谁有空来奶茶铺打工?工资日结!”

当线程数 < maximumPoolSize,线程池会再雇些“临时工”来应对高峰。(这是队列满的前提下)

就像:

原来 3 个员工全力干活,本子上 2 个订单排队,结果来了 20 个顾客,直接再加 2 名临时工顶上,最大能加到 5 个。

🧨 4. 再忙也招不动了,咋办?(拒绝策略 handler)

如果你已经雇满了 5 个人,本子也记满了,这时候再来顾客,怎么办?
这时候就看你咋拒绝了,通常有以下几种拒绝策略:

  • 老板摆烂法(AbortPolicy):直接告诉顾客“对不起,做不出来了”,然后抛异常。
  • 丢掉最老的(DiscardOldestPolicy):告诉点单小助手“把最早记下的订单撕了吧,优先处理新订单”。
  • 悄悄丢掉新来的(DiscardPolicy):新来的顾客只能看着菜单流泪而去。
  • 自己动手丰衣足食(CallerRunsPolicy):让顾客自己动手做自己的奶茶。

🌸 5. 淡季来了,临时工该走走了(keepAliveTime)

旺季过去了,订单变少了,兼职工们也没啥事干了。你设置了个规则:

“如果 10 秒钟都没人点单,临时工就自动下班。”

于是,临时工们一个个拍拍屁股走人,店里又回归到 3 个固定员工的状态。


 简而言之:

  1. 3 个固定工人:最少线程数。
  2. 排队机制:能排队就不着急招人。
  3. 高峰临时工:队列满了,再加人,最多加到 maximumPoolSize。
  4. 忙不过来了的应对策略:要么拒绝、要么丢订单、要么让顾客自助。
  5. 临时工按需解散:空闲太久就“回家躺平”。

阻塞队列的作用——奶茶店扩容的秘密武器

还记得我们之前聊的 “线程池奶茶铺” 吗?现在咱们要揭秘一个幕后英雄:阻塞队列。这玩意就像奶茶店里的点单小助手,但它不只是简单记账,它还有独特的“智慧”和“耐心”。

🧠 阻塞队列到底有啥用?

阻塞队列 = 一个既能存单又能管理员工节奏的智能本子。

想象一下,你的奶茶店高峰期来了——顾客像潮水一样涌进来。你有 3 个固定员工(corePoolSize),全力以赴地在摇奶茶,但订单还是接连不断。这时候,阻塞队列就开始展现它的魔力了。

🎯 1. 阻塞=“慢慢来,别慌!”

当顾客蜂拥而至时,阻塞队列会说:

“别慌!先让我把这些订单都记在本子上,员工们干完手头活后会继续处理的。”

通俗解释:

  • 如果顾客少,队列就闲着,员工甚至可以打会儿瞌睡(线程进入wait状态,释放CPU资源)。
  • 如果顾客多,队列就顶上,帮你缓冲住这波流量。只要队列没写满,就不用立刻扩招。

🤹 2. 为什么先排队不先扩招?

你可能想问:“为啥不直接招人?人多力量大啊!”

原因1:扩招有“管理成本”
雇人可不是拍拍脑袋就行。每次新招一个线程,都得加锁(全局锁),这会让程序效率下降。就像每次找兼职时,经理都得停下手头工作,去审核简历。

原因2:排队比扩招便宜
订单多?先堆到本子上!员工们干完手头活就接着处理新的订单,没必要每来一个新顾客就立刻扩招。

就像地铁上:先让大家往里挤一挤,总比一开始就加派新车厢高效。

🧨 3. 任务真的太多了咋办?

订单排到第 5 页了,队列也写满了,怎么办?这时候领导就得思考“扩编”了:

  • 先找临时工(线程数 > corePoolSize)。
  • 如果连临时工都顶不住,才开始拒绝新订单。
// 线程池初始化
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    3,    // corePoolSize:固定员工数3个
    6,    // maximumPoolSize:最多雇6个(包含临时工)
    10,   // keepAliveTime:临时工超过10s没活干就下班
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(5) // 队列最多记5个订单
);

// 提交任务模拟顾客下单
for (int i = 1; i <= 15; i++) {
    int orderNum = i;
    pool.execute(() -> {
        System.out.println("正在处理订单 " + orderNum);
        try {
            Thread.sleep(2000); // 模拟奶茶制作耗时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
pool.shutdown();

🌀 线程池的线程复用原理——打破“一次性工人”魔咒

🎯 以前的世界:一人干一活,干完就走

在没有线程池的原始时代,创建一个线程(Thread),就像雇一个临时奶茶工。每次来一单,咱就得新招一人,干完活就让他走人。

  • 这意味着:"每单一工"。人力成本高,管理混乱,CPU浪费得不行。
// 原始的线程使用方式
Thread thread = new Thread(() -> {
    System.out.println("做一杯奶茶...");
});
thread.start();

缺点:

  1. 来一单就招一个人,开销大。
  2. 招了就走,后续再来单还得重新招,毫无复用。

这不就像每次卖奶茶都重新招聘员工一样吗?完全不划算!

🌈 线程池:员工不走,订单不断

线程池是个什么鬼?它打破了“一个任务=一个线程”的魔咒,改用 “少数员工轮流干活” 的模式。

核心秘诀:

  1. 先雇固定数量的员工(线程)。
  2. 这些员工不是干完就走,而是一直待在店里。
  3. 只要本子上(阻塞队列)有新订单,他们就从本子上取出来继续干。
  4. 没订单时,员工就打瞌睡(wait),有订单时再醒来干活(notify)。

⚙️ 底层原理揭秘

每个线程池里的员工,干的其实是一个“死循环任务”:
“只要店里没关门,就一直看本子,谁的订单排在前面,就接着干。”

翻译成人话:(并不是每次执⾏任务都会调⽤ Thread.start() 来创建新线程,⽽是让每个线程去执⾏⼀ 个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执⾏,如果有则直接执⾏,也就是调⽤ 任务中的 run ⽅法

class Worker implements Runnable {
    private BlockingQueue<Runnable> taskQueue;

    public Worker(BlockingQueue<Runnable> queue) {
        this.taskQueue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                // 从队列中拿任务,队列为空时自动阻塞
                Runnable task = taskQueue.take();
                task.run(); // 直接调用任务的run方法
            } catch (InterruptedException e) {
                System.out.println("线程被中断,准备下班!");
                break;
            }
        }
    }
}

解释:

  • taskQueue.take():本子上有订单就取出来干,没有就一直睡觉。
  • task.run():直接执行任务的run()方法,没必要新建线程了。

🚀 为什么这种模式牛?

  1. 减少开销:
    • 不再频繁创建和销毁线程,CPU和内存资源更省。
  2. 高效调度:
    • 一个线程反复从队列中取任务干活,和流水线作业一样高效。
  3. 线程复用:
    • 告别“临时工文化”,员工都变成了“全能老油条”,有活就干,没活就歇。

🎯 最后的话:线程池牛,点赞关注更牛!

好了,今天咱们从“奶茶铺”一路聊到“线程池”,看清了线程池是如何通过阻塞队列线程复用灵活调度,高效完成任务,避免了“每单一工”的低效做法。

如果你觉得这篇内容有趣又有用
👉 点个赞! 你的认可是我继续输出优质内容的最大动力。
👉 点个关注! 不迷路,带你继续探索更多有趣的技术干货。

我们下次见,记得喝杯“线程池奶茶”再走哦! ☕😄

——码到功成,线程不慌! 🧡💻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Starry-Walker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值