【042】还在 new Thread?Executor 框架把线程管得服服帖帖,面试问烂了!

在这里插入图片描述


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

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

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


零、引入

在这里插入图片描述
王二的脸涨得像煮熟的虾,手里攥着鼠标,屏幕上的日志红得刺眼 —— 他写的商品秒杀代码,一压测就崩,满屏都是ThreadDeath和OutOfMemoryError。领导站在身后,鼻孔里的气都快喷到他后颈:“你这代码里的线程,比菜市场的苍蝇还乱,谁教你这么 new Thread 的?”

王二缩着脖子,想说 “网上都这么写”,话到嘴边又咽了回去。隔壁工位的哇哥把烟蒂摁灭在烟灰缸里,慢悠悠开口:“这不能怪他,多数菜鸟都把线程当散兵游勇,没见过正规军的章法。” 他顿了顿,指了指王二的屏幕,“Executor 框架早把线程管得明明白白,你偏不用,这不是自讨苦吃?”

点赞 + 关注,跟着哇哥和王二,用工厂管工人的道理,把 Executor 框架嚼碎了咽下去,下次再写并发代码,领导都得高看你两眼!

在这里插入图片描述

一、王二的 “线程蛊”:new Thread 的三大罪状

在这里插入图片描述

王二的代码是典型的 “菜鸟写法”—— 每次用户下单,就 new 一个 Thread 去处理库存,代码长这样:

package cn.tcmeta.threads;


import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: 王二的坑:new Thread满天飞,线程乱成一锅粥
 * @version: 1.0.0
 */
public class ThreadDisasterSample {
    // 模拟秒杀减库存
    private static void reduceStock(String productId) {
        try {
            // 模拟库存操作耗时
            TimeUnit.MILLISECONDS.sleep(50);
            System.out.println(Thread.currentThread().getName() + ":商品" + productId + "库存减1");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    static void main() {
        // 模拟100个秒杀请求
        for (int i = 0; i < 100; i++) {
            String productId = "P" + i;
            // 每次请求都new一个Thread,灾难的开始
            new Thread(() -> reduceStock(productId), "Thread-" + i).start();
        }
    }
}

在这里插入图片描述
运行起来,日志里的线程名杂乱无章,压测到 1000 并发直接 OOM——JVM 里的线程比头发还多,内存扛不住了。

哇哥看了直摇头:“这代码犯了三个死罪,跟三十年代工厂里的散工一样,没章法:

  • 线程复用差:new 一个 Thread 就占 1MB 左右内存,1000 个就是 1GB,内存直接炸;
  • 管理混乱:线程什么时候启动、结束,全是野的,出了问题连日志都查不清;
  • 任务与线程耦合:你既要写‘减库存’的任务,又要管‘线程’的生命周期,精力全花在杂事上。”

王二挠头:“那咋办?总不能不用线程吧?”

“用 Executor 框架,” 哇哥把茶杯往桌上一墩,“它就像工厂的工头,你只需要把‘减库存’的任务交给他,线程怎么开、怎么复用、怎么关,全不用你管 —— 这叫‘任务与线程解耦’,懂?”

二、用 “工厂管工人” 讲透 Executor 核心:解耦才是王道

在这里插入图片描述
哇哥拉过王二的草稿纸,画了个简陋的工厂示意图:

“你看,工厂里的工人(线程)、要做的零件(任务)、管工人的工头(Executor),三者各司其职。你之前的代码,是自己又当工人又当工头,累死还干不好。Executor 就是这个工头,你只需要把零件交给他,他来安排工人干活。”

✅ Executor 框架核心:一套 “管线程的规矩”

Executor 框架不是一个类,是一套接口和实现类的总称,核心是 “解耦任务提交和线程管理”
在这里插入图片描述

📢 核心接口详解(王二记在烟盒上)

  • Executor:最顶层,只有一个方法execute(Runnable command)—— 把任务交出去,不管怎么执行;
  • ExecutorService:扩展了 Executor,加了线程池的生命周期管理,比如submit()(提交任务带返回值)、shutdown()(关闭线程池);
  • ThreadPoolExecutor:核心实现类,真正的 “工头”,管理线程池的创建、任务分配、线程回收;
  • Executors:工具类,帮你快速创建线程池(但生产环境要慎用,后面讲坑)。

“简单说,Executor 是‘我要干活’,ExecutorService 是‘我要干活,还要知道活干没干完’,ThreadPoolExecutor 是‘我来安排人干活,保证干得又快又好’,” 哇哥总结,“你之前的代码,连‘工头’都没有,工人自然乱成一团。”

三、改造代码:ExecutorService 让线程 “收编归队”

哇哥手把手教王二改代码,把 new Thread 换成 ExecutorService,只用了几行,代码就清爽了:

package cn.tcmeta.threads;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

// 优化版:用ExecutorService管理线程,线程池来干活
public class ExecutorRescue {
    // 模拟秒杀减库存(任务不变)
    private static void reduceStock(String productId) {
        try {
            TimeUnit.MILLISECONDS.sleep(50);
            System.out.println(Thread.currentThread().getName() + ":商品" + productId + "库存减1");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 1. 创建固定线程池:10个线程(工头带10个工人)
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        // 2. 提交100个任务(100个零件交给工头)
        for (int i = 0; i < 100; i++) {
            String productId = "P" + i;
            // 提交任务,不用管线程怎么开
            executorService.submit(() -> reduceStock(productId));
        }

        // 3. 关闭线程池:先停止接收新任务,等已提交的任务完成
        executorService.shutdown();
        // 等待所有任务完成,最多等1分钟
        if (!executorService.awaitTermination(1, TimeUnit.MINUTES)) {
            // 没完成就强制关闭
            executorService.shutdownNow();
        }

        System.out.println("所有秒杀任务处理完毕");
    }
}

在这里插入图片描述
王二看着日志里重复出现的pool-1-thread-1,眼睛亮了:“线程真的复用了!之前 100 个任务开 100 个线程,现在 10 个就够了!”

“这才是 Executor 的精髓,” 哇哥点了根烟,“线程池里的线程是‘常驻军’,任务做完不销毁,等着下一个任务,省了创建销毁线程的开销 —— 就像工厂的工人,不会做完一个零件就辞职,等着下一个零件送过来。”

四、ExecutorService 常用方法:王二的 “工头指令集”

哇哥怕王二记混,把常用方法总结成 “工头的指挥手势”,贴在他显示器上:

  • submit(Runnable):提交无返回值的任务,返回 Future 对象(可以判断任务是否完成);
  • submit(Callable):提交有返回值的任务,Future 的 get () 方法能拿结果;
  • shutdown():平缓关闭,拒绝新任务,等待已提交任务完成;
  • shutdownNow():强制关闭,中断正在执行的任务,返回未执行的任务;
  • awaitTermination(long, TimeUnit):等待线程池关闭,超时返回 false。

👉 用 Callable 拿任务返回值

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

// 用Callable获取任务返回值
public class ExecutorCallableDemo {
    // 有返回值的任务:计算商品折扣后价格
    private static Callable<Double> calculateDiscountPrice(String productId, double price) {
        return () -> {
            // 模拟计算耗时
            Thread.sleep(50);
            // 假设折扣是9折
            double discountPrice = price * 0.9;
            System.out.println(Thread.currentThread().getName() + ":商品" + productId + "折扣价计算完成");
            return discountPrice;
        };
    }

    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 提交Callable任务
        Future<Double> future = executor.submit(calculateDiscountPrice("P1001", 6999.0));

        // 干别的事,不用等结果
        System.out.println("主线程:我先处理其他事情...");

        // 拿结果(如果没完成会阻塞,直到完成)
        double discountPrice = future.get();
        System.out.println("商品P1001折扣价:" + discountPrice + "元");

        executor.shutdown();
    }
}

五、总结:Executor 核心心法(王二刻在心里)

  • 别再 new Thread:线程是宝贵资源,复用才是王道,Executor 帮你管线程;
  • Executor 是工头:你只提交任务,线程的创建、复用、销毁全交给它;
  • ExecutorService 管生命周期:submit 提交任务,shutdown 关闭线程池,别忘收尾;
  • 线程池是核心:FixedThreadPool、CachedThreadPool 按需选,避免线程泛滥。

➡️ 哇哥的面试彩蛋

“面试时被问‘为什么用 Executor 而不是 new Thread’,你就说三点,” 哇哥弹了弹烟灰,“一是线程复用,减少开销;二是解耦任务和线程,代码简洁;三是便于管理,能控制线程数量,防止 OOM—— 这三点一讲,面试官就知道你不是背概念的菜鸟。”

在这里插入图片描述

关注我,下一篇咱们扒一扒 ThreadPoolExecutor 的底细 —— 这个 “工头” 是怎么管理工人的?核心参数是什么意思?面试时被问 “核心线程数和最大线程数的区别”,怎么用流水线的例子怼回去?让你把 Executor 框架的根挖透!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值