欢迎光临
我们一直在努力

让小白一眼爱上的Java 线程池入门与面试

目录

一、Java 线程池的继承结构

常用的是哪种?

ForkJoinPool 什么场合用?

二、线程池是什么(为什么要用)

二、核心组件(ThreadPoolExecutor 的七大参数)

① corePoolSize(核心线程数)

② maximumPoolSize(最大线程数)

③ keepAliveTime(空闲存活时间)

④ TimeUnit(时间单位)

⑤ workQueue(任务队列)

⑥ threadFactory(线程工厂)

⑦ RejectedExecutionHandler(拒绝策略)

三、任务执行流程(面试必问)

四、四种快捷创建方式(不推荐!)

五、实战:从零创建一个线程池(手把手)

第一步:建一个线程池

第二步:理解每个任务怎么跑的

第三步:验证线程池状态

六、线程池的五种状态

七、execute() 和 submit() 的区别

八、如何设置线程池大小(面试高频)

一、阿里手册确实禁止

二、线程池用的是谁的资源?

三、那微服务里线程池到底是谁的?

一句话

九、面试话术汇总

Q1:线程池的七大参数是什么?执行流程是怎样的?

Q2:为什么阿里巴巴禁止使用 Executors 创建线程池?

Q3:核心线程数怎么设置?

Q4:shutdown() 和 shutdownNow() 的区别?

Q5:线程池的拒绝策略有哪些?什么时候用哪种?

Q6:线程池里的线程什么时候被销毁?

Q7: execute 和 submit 哪个好?

Q8:线程池监控怎么做?

Q9:线程池提交任务时,核心线程都在忙、队列也满了,这时是先创建临时线程还是先拒绝?

Q10:subimit 和 execute 异常丢失具体指什么?

面试刁钻题 1:线程池中的线程抛出异常会怎样?这个线程会被销毁吗?

面试刁钻题 2:用 submit 提交,future.get() 阻塞等待时,这个线程是否算在"活跃线程数"里?

面试刁钻题 3:线程池的 prestartAllCoreThreads() 是干什么的?预热有什么用?

面试刁钻题 4:一个线程池的 corePoolSize=0,maximumPoolSize=10,队列是 SynchronousQueue,会怎么样?这和 newCachedThreadPool 有什么不同?

面试刁钻题 5:线程池的队列满了后,新任务是立即创建临时线程执行,还是等队列任务被取走一个再执行?

面试刁钻题 6:线程池什么时候从队列里取任务?

面试刁钻题 7:线程池的线程做完任务后,是直接销毁还是怎么回到池子里?

十、线程池的三个核心问题

10.1 什么时候用

10.2 什么时候别用

10.3 什么时候注意 ThreadLocal

十一、一句话速记



一、Java 线程池的继承结构

整个线程池体系从顶层接口到底层实现,四个层级:

Executor(顶级接口)
│   只有一个方法:void execute(Runnable command)
│   简单说:你给我一个任务,我帮你跑

└── ExecutorService(子接口)
  │   扩展了三大能力:
  │   ① 提交有返回值的任务(submit(Callable))
  │   ② 关闭线程池(shutdown/shutdownNow)
  │   ③ 批量提交(invokeAll/invokeAny)
  │
  ├── ThreadPoolExecutor(最常用的实现类)
  │   普通阻塞队列线程池,七大参数在这里定义
  │   用来执行大部分日常任务
  │
  └── ForkJoinPool(分支合并线程池)
      工作窃取算法,专治分治任务(把大任务拆小)
      Java 8 的 parallelStream 底层就是这个

常用的是哪种?

你 90% 的场景用 ThreadPoolExecutor。

// 最常用的是这个
ThreadPoolExecutor executor = new ThreadPoolExecutor(
   2, 4, 60, TimeUnit.SECONDS,
   new ArrayBlockingQueue<>(100)
);

ForkJoinPool 什么场合用?

// 大任务拆小:计算 1 到 100 万的和
// RecursiveTask 或 RecursiveAction
// parallelStream 底层就是 ForkJoinPool
int sum = IntStream.range(1, 1_000_000).parallel().sum();
// ForkJoinPool.commonPool() 默默在干活

和 ThreadPoolExecutor 的核心区别:

ThreadPoolExecutorForkJoinPool
队列 一个公共任务队列 每个线程一个自己的队列
任务获取 从公共队列取 先拿自己的,自己的空了去偷别人的
适用场景 通用任务执行 分治任务(大拆小)

一般面试只需要记住:Executor → ExecutorService → ThreadPoolExecutor / ForkJoinPool 这个继承结构,用的最多的就是 ThreadPoolExecutor,ForkJoinPool 知道是 parallelStream 的底层就行了。


二、线程池是什么(为什么要用)

线程池就是一个装线程的"池子",里面有若干个已经创建好的线程,等着你来派活。

没有线程池时:
来一个任务 → new 一个线程 → 用完就扔掉(销毁)
→ 频繁创建/销毁线程,性能差

有线程池时:
先创建一个池子,里面放 5 个线程
来一个任务 → 从池子里拿一个空闲线程去执行 → 执行完线程回到池子
→ 线程复用,减少创建销毁开销

为什么需要它:

问题线程池怎么解决
频繁创建/销毁线程,CPU 开销大 线程复用,创建一次用多次
线程数量无限制,可能 OOM 控制最大线程数,超出的排队
没法管理线程 统一管理:监控线程状态、任务队列
系统负载不可控 任务太多时排队或拒绝

二、核心组件(ThreadPoolExecutor 的七大参数)

Java 的线程池核心实现是 ThreadPoolExecutor,它要用到七个参数来构造(面试必背):

public ThreadPoolExecutor(
   int corePoolSize,                // ① 核心线程数
   int maximumPoolSize,             // ② 最大线程数
   long keepAliveTime,              // ③ 空闲线程存活时间
   TimeUnit unit,                   // ④ 时间单位
   BlockingQueue<Runnable> workQueue, // ⑤ 任务队列
   ThreadFactory threadFactory,     // ⑥ 线程工厂
   RejectedExecutionHandler handler  // ⑦ 拒绝策略
)

① corePoolSize(核心线程数)

常驻线程的数量,即使这些线程闲着也不销毁(除非设置了 allowCoreThreadTimeOut)。

// 假如设 corePoolSize = 5,线程池里永远至少有 5 个线程活着
// 来 3 个任务 → 用其中 3 个线程,剩下 2 个空闲
// 来 10 个任务 → 5 个线程全跑满了

② maximumPoolSize(最大线程数)

线程池最多能有的线程数。当核心线程都忙且队列也满了,就会创建新的临时线程直到这个数。

// 假如 corePoolSize=5, maximumPoolSize=10, 队列容量=100
// 来了 200 个任务 → 5 个核心线程先跑
// 5 个跑满了 → 后面 100 个排队
// 队列也满了 → 再来任务就创建临时线程(最多到 10 个)
// 10 个线程也满了 → 触发拒绝策略

③ keepAliveTime(空闲存活时间)

临时线程空闲了能活多久。超时没接到新任务就销毁。

// 假如 keepAliveTime = 60 秒
// 临时线程干完活后,没事干等 60 秒 → 自动销毁,释放资源

④ TimeUnit(时间单位)

TimeUnit.SECONDS    // 秒
TimeUnit.MILLISECONDS  // 毫秒
TimeUnit.MINUTES   // 分钟

⑤ workQueue(任务队列)

核心线程满了,新任务先排队。排队也满了才创建临时线程。

常见队列:

队列特点使用场景
ArrayBlockingQueue 有界队列,容量固定 常用,能防止 OOM
LinkedBlockingQueue 可设容量的链表队列 默认无界(Integer.MAX_VALUE),小心 OOM
SynchronousQueue 容量为 0,不排队,直接交给线程 newCachedThreadPool 用这个
PriorityBlockingQueue 支持优先级排序 需要优先处理某类任务时
DelayQueue 延迟执行 定时任务场景

⑥ threadFactory(线程工厂)

给线程取名字的。默认名字是 pool-1-thread-1,出问题你根本看不出来这是哪个池子的。

// ✅ 正确做法:自定义名字
ThreadFactory namedFactory = new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger(1);

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("order-service-pool-" + counter.getAndIncrement());
return t;
}
};

⑦ RejectedExecutionHandler(拒绝策略)

线程池忙不过来(线程满了、队列满了)时怎么办。

策略行为
AbortPolicy(默认) 抛 RejectedExecutionException
CallerRunsPolicy 谁提交的任务谁自己执行(在提交者的线程里跑)
DiscardPolicy 静默丢弃,不抛异常
DiscardOldestPolicy 丢弃队列里最老的那个任务,再试一次

// 拒绝策略一般用 CallerRunsPolicy 最安全
// 因为提交者(比如 Tomcat 线程)执行,起到了"反压"作用
new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy());
// 这样至少任务不会丢,只是慢一点


三、任务执行流程(面试必问)

这是线程池最核心的知识点,七步递进:

提交一个任务


① 当前线程数 < corePoolSize?
├── 是 → 创建新线程执行任务
└── 否 → 进入下一步


② 尝试入队(workQueue.offer(task))
├── 入队成功 → 任务在队列里等着,线程空闲时取出来执行
└── 入队失败 → 进入下一步(队列满了,比如 ArrayBlockingQueue(100) 满了)


③ 当前线程数 < maximumPoolSize?
├── 是 → 创建新临时线程执行任务
└── 否 → 执行拒绝策略 handler.rejectedExecution(task)

简化速记版:

核心线程跑 → 满了去排队 → 队满了扩线程 → 扩满了就拒


四、四种快捷创建方式(不推荐!)

Java 提供了 Executors 工具类,一行代码创建线程池——但阿里巴巴手册明确禁止:

// ① 固定线程数
ExecutorService fixed = Executors.newFixedThreadPool(5);
// 问题:LinkedBlockingQueue 无界(最大 Integer.MAX_VALUE)
// 任务积压 → OOM

// ② 单线程
ExecutorService single = Executors.newSingleThreadExecutor();
// 同上,无界队列

// ③ 缓存线程池
ExecutorService cached = Executors.newCachedThreadPool();
// 问题:maxPoolSize = Integer.MAX_VALUE
// 请求量大时 → 创建无限多线程 → OOM

// ④ 定时任务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(5);
// 同上,maxPoolSize 无限

阿里巴巴为什么禁止:

创建方式风险后果
newFixedThreadPool 无界队列 任务积压导致 OOM
newCachedThreadPool 无限线程 线程创建太多导致 OOM
newSingleThreadExecutor 无界队列 同 fixed
newScheduledThreadPool 无限线程 同 cached

正确做法:自己 new ThreadPoolExecutor 显式传参


五、实战:从零创建一个线程池(手把手)

第一步:建一个线程池

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadPoolDemo {

public static void main(String[] args) {
// 1. 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize = 2
4, // maximumPoolSize = 4
60, // keepAliveTime = 60 秒
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), // 队列容量 = 3
new NamedThreadFactory("order-pool"), // 自定义线程名
new ThreadPoolExecutor.CallerRunsPolicy() // 提交者自己执行
);

// 2. 提交 8 个任务
for (int i = 1; i <= 8; i++) {
int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()
+ " 正在执行任务 " + taskId);
try {
Thread.sleep(2000); // 模拟任务执行 2 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 执行完任务 " + taskId);
});
}

// 3. 关闭线程池
executor.shutdown();
}

// 自定义线程工厂:给线程取个好名字
static class NamedThreadFactory implements ThreadFactory {
private final String prefix;
private final AtomicInteger counter = new AtomicInteger(1);

public NamedThreadFactory(String prefix) {
this.prefix = prefix;
}

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName(prefix + "-" + counter.getAndIncrement());
return t;
}
}
}

第二步:理解每个任务怎么跑的

核心池:2 个线程(order-pool-1, order-pool-2)
队列:3 个位置
最大线程:4 个

提交任务 1 → order-pool-1 执行(没到 corePoolSize,创建新线程)
提交任务 2 → order-pool-2 执行
提交任务 3 → 入队(核心线程满了)
提交任务 4 → 入队
提交任务 5 → 入队
提交任务 6 → 队列满了!创建临时线程 order-pool-3 执行
提交任务 7 → 队列满了!创建临时线程 order-pool-4 执行
提交任务 8 → 线程数到 maximumPoolSize=4 了!触发拒绝策略(CallerRunsPolicy → main 线程自己执行)

最终:order-pool-1, -2, -3, -4 + main 线程 = 5 个线程同时执行

第三步:验证线程池状态

// 提交完任务后,打印线程池状态
System.out.println("=== 线程池状态 ===");
System.out.println("活跃线程数:" + executor.getPoolSize());
System.out.println("核心线程数:" + executor.getCorePoolSize());
System.out.println("队列中任务数:" + executor.getQueue().size());
System.out.println("已完成任务数:" + executor.getCompletedTaskCount());


六、线程池的五种状态

shutdown()
RUNNING ──────────────→ SHUTDOWN
│ (不收新任务,跑完队列里的)
│ │
│ shutdownNow() │
├────────────────────→ STOP
│ (不收新任务,也不跑队列,中断正在跑的)
│ │
│ │ 队列空 + 所有线程终止
│ ▼
│ TIDYING
│ │
│ │ terminated() 钩子执行完
│ ▼
└──────────────────── TERMINATED
executor.shutdown(); // 优雅关闭:等队列跑完
executor.shutdownNow(); // 暴力关闭:中断正在跑的,返回队列里的任务
executor.isShutdown(); // 是否已关闭
executor.isTerminated(); // 是否已完全终止
executor.awaitTermination(10, TimeUnit.SECONDS); // 等待线程池关闭(最多等 10 秒)

面试常问:shutdown 和 shutdownNow 区别?

shutdown:不接受新任务,但会等队列里已有的任务执行完,再关闭。shutdownNow:不接受新任务,正在执行的线程调 interrupt(),队列里的任务直接返回,不执行了。


七、execute() 和 submit() 的区别

// execute:提交 Runnable,没有返回值,异常直接打印
executor.execute(() -> {
System.out.println("没有返回值");
});

// submit:提交 Callable,返回 Future 可以拿结果或异常
Future<String> future = executor.submit(() -> {
return "有返回值";
});
String result = future.get(); // 阻塞等待结果

// 如果 submit 的任务抛异常,future.get() 会抛出
// 而 execute 的异常直接打到控制台,你可能不知道

面试常问:

// execute 异常丢失问题
executor.execute(() -> {
throw new RuntimeException("报错了"); // 控制台打印,没人知道
});

// submit 异常
Future<?> future = executor.submit(() -> {
throw new RuntimeException("报错了");
});
future.get(); // 抛 ExecutionException,你能捕获到

结论:生产环境用 submit,不要用 execute,至少异常不会无声无息丢。


八、如何设置线程池大小(面试高频)

公式:

CPU 密集型任务(大量计算):
线程数 = CPU 核数 + 1

IO 密集型任务(大量网络/磁盘等待):
线程数 = CPU 核数 × 2 或更大

更精确的公式(美团方案):
线程数 = CPU核数 × (1 + 等待时间 / 计算时间)
// 获取 CPU 核数
int cpus = Runtime.getRuntime().availableProcessors();

// CPU 密集型
ThreadPoolExecutor cpuPool = new ThreadPoolExecutor(
cpus + 1, cpus + 1, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000)
);

// IO 密集型(比如调远程服务)
ThreadPoolExecutor ioPool = new ThreadPoolExecutor(
cpus * 2, cpus * 2, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000)
);

为什么 CPU 密集型不能太多:

  • 假如 4 核 CPU,开 8 个线程

  • 1 个线程在跑计算,其他 7 个在抢 CPU

  • 线程切换本身就要消耗 CPU 时间

  • 反而比 4 个线程跑得更慢

为什么 IO 密集型可以多:

  • 线程在等数据库/远程服务返回时,CPU 是空闲的

  • 更多的线程可以让 CPU 在等待期间干别的事

  • 不浪费 CPU 时间片


九、面试话术汇总

Q1:线程池的七大参数是什么?执行流程是怎样的?

话术:七大参数是 corePoolSize、maximumPoolSize、keepAliveTime、TimeUnit、workQueue、threadFactory、RejectedExecutionHandler。执行流程是:先看核心线程有没有满,没满就创建核心线程;满了就入队;队满了就创建临时线程直到最大线程数;再满就执行拒绝策略。

Q2:为什么阿里巴巴禁止使用 Executors 创建线程池?

话术:三个原因。newFixedThreadPool 和 newSingleThreadExecutor 用了无界的 LinkedBlockingQueue,队列容量是 Integer.MAX_VALUE,任务积压会导致 OOM。newCachedThreadPool 的最大线程数是 Integer.MAX_VALUE,高并发下会创建无限线程也导致 OOM。而且默认的线程名没有业务含义,出问题不好排查。所以推荐自己 new ThreadPoolExecutor 显式传参。

Q3:核心线程数怎么设置?

话术:分 CPU 密集型和 IO 密集型。CPU 密集型设 CPU 核数+1,避免线程切换浪费 CPU。IO 密集型设 CPU 核数×2 或更大,因为线程在等待 IO 时 CPU 是空闲的。更精确的公式是:线程数 = CPU核数 × (1 + 等待时间/计算时间),但实际还是需要通过压测来验证。

Q4:shutdown() 和 shutdownNow() 的区别?

话术:shutdown 是优雅关闭,不再接收新任务但会执行完队列里已有的任务。shutdownNow 是暴力关闭,不再接收新任务,正在执行的任务调 interrupt() 中断,队列里还没执行的任务直接返回。生产环境优先用 shutdown,除非需要快速释放资源。

Q5:线程池的拒绝策略有哪些?什么时候用哪种?

话术:四种。AbortPolicy(默认,抛异常)、CallerRunsPolicy(谁提交谁执行)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢最老任务再试)。生产环境最推荐 CallerRunsPolicy,因为让提交者自己执行起到了反压作用,不会丢任务,只是请求变慢。AbortPolicy 如果调用方没处理好异常,任务就丢了。

Q6:线程池里的线程什么时候被销毁?

话术:核心线程默认不销毁,除非设置了 allowCoreThreadTimeOut(true)。非核心线程(超过 corePoolSize 创建的那些)在空闲时间超过 keepAliveTime 后自动销毁。所以如果任务量降下来了,临时线程会自动回收,释放资源。

Q7: execute 和 submit 哪个好?

话术:生产环境用 submit。execute 提交的任务抛异常只会打印到控制台,不会通知调用方,容易造成异常静默丢失。submit 返回 Future,调用 future.get() 会抛 ExecutionException,能捕获并处理异常。而且 submit 支持 Callable 有返回值。

Q8:线程池监控怎么做?

话术:通过 ThreadPoolExecutor 自带的 getPoolSize()、getActiveCount()、getQueue().size()、getCompletedTaskCount() 等方法获取实时状态。还可以通过 beforeExecute 和 afterExecute 钩子方法做埋点,记录每个任务的执行耗时。生产环境一般对接 Prometheus + Grafana 做可视化监控。

Q9:线程池提交任务时,核心线程都在忙、队列也满了,这时是先创建临时线程还是先拒绝?

话术:先创建临时线程。完整流程是:① 核心线程 < corePoolSize → 创建;② 尝试入队;③ 入队失败(队列满了)且线程数 < maximumPoolSize → 创建临时线程;④ 线程数已到 maximumPoolSize → 执行拒绝策略。

Q10:subimit 和 execute 异常丢失具体指什么?

话术:execute 的任务如果抛了 RuntimeException,线程池会捕获后打印到 System.err,但调用方完全不知道,异常静默丢失。submit 的任务抛异常,异常会被包装在 Future 里,调用 future.get() 时会抛 ExecutionException,调用方可以 catch 到并处理。所以线上代码建议用 submit + 处理 future。

面试刁钻题 1:线程池中的线程抛出异常会怎样?这个线程会被销毁吗?

话术:会。Worker 线程在执行任务时如果抛异常,runWorker 方法会捕获到,执行 afterExecute 钩子,然后线程退出。线程池会判断当前线程数是否小于核心线程数,如果是就创建一个新 Worker 补充进来。所以线程池并不会因为这个线程挂了就一直少一个线程,它会自动补充到 corePoolSize。

面试刁钻题 2:用 submit 提交,future.get() 阻塞等待时,这个线程是否算在"活跃线程数"里?

话术:算的。future.get() 阻塞的是调用 get() 的那个线程(通常是业务主线程),不是线程池里的 Worker 线程。Worker 线程在任务执行完后已经回到线程池了。get() 阻塞的是外面的线程,不是池子里的线程,池子里的活跃线程数已经是空闲状态了。

面试刁钻题 3:线程池的 prestartAllCoreThreads() 是干什么的?预热有什么用?

话术:默认情况下核心线程是懒加载的,来第一个任务才创建。prestartAllCoreThreads() 会提前创建所有核心线程。预热的作用是避免第一个请求因为线程创建而变慢,常用于对延迟敏感的系统。比如网关服务,启动时先预热好线程,第一个请求来的时候线程已经存在了。

面试刁钻题 4:一个线程池的 corePoolSize=0,maximumPoolSize=10,队列是 SynchronousQueue,会怎么样?这和 newCachedThreadPool 有什么不同?

话术:SynchronousQueue 队列容量为 0,不存储任务,必须直接交给线程。因为 corePoolSize=0,来任务时会先尝试创建临时线程。这与 newCachedThreadPool 行为类似,区别是 newCachedThreadPool 的 keepAliveTime=60s(临时线程存活 60 秒),而如果这里自己设 corePoolSize=0 + SynchronousQueue + keepAliveTime=0,那临时线程干完活就立刻销毁了,几乎等于来一个任务就 new 一个线程、用完就销毁,性能极差。

面试刁钻题 5:线程池的队列满了后,新任务是立即创建临时线程执行,还是等队列任务被取走一个再执行?

话术:立即执行。offer() 入队失败返回 false,不会等队列有位置。offer 失败后走 addWorker() 加新线程并直接提交任务。所以当任务队列满的时候,新来的任务是由临时线程立即执行的,不会排队等待队列空出来。

面试刁钻题 6:线程池什么时候从队列里取任务?

话术:Worker 线程执行完当前任务后,会去 workQueue.poll() 或 take() 取下一个任务。Worker 的 runWorker 方法是死循环,不断取任务执行。取不到就超时后退出(如果是临时线程)或者一直阻塞等(如果是核心线程)。所以队列里的任务不是主动被"推"出去的,而是线程空下来了自己"拉"的。

面试刁钻题 7:线程池的线程做完任务后,是直接销毁还是怎么回到池子里?

话术:做完任务后,Worker 线程进入无限循环:取下一个任务 → 执行 → 取下一个 → 执行。如果取不到任务,分两种情况:核心线程调 workQueue.take() 阻塞等,一直活着;临时线程调 workQueue.poll(keepAliveTime, TimeUnit),超时后还没取到就退出销毁。所以线程做完任务并没有"回到池子"这个概念,它本来就在池子里,只是从忙碌状态变成了等任务状态。


十、线程池的三个核心问题

10.1 什么时候用

场景要不要线程池
异步执行耗时操作(发短信、发邮件) ✅ 要
多任务并行处理(同时查询多个服务) ✅ 要
定时/周期任务 ✅ 要
Tomcat 接收 HTTP 请求 ✅ Tomcat 自己已经用了线程池(你不需要自己建)
同步调一个方法 ❌ 不需要
简单的 for 循环 ❌ 不需要

10.2 什么时候别用

// ❌ 滥用:方法内部创建线程池,调用一次就创建一个
public void doSomething() {
ExecutorService pool = Executors.newFixedThreadPool(5); // 每次调都 new!
pool.submit(() -> { … });
pool.shutdown();
}
// 这个和每次 new Thread() 没什么区别

// ✅ 正确:线程池是全局的
public class OrderService {
private static final ExecutorService POOL = createPool();

public void doSomething() {
POOL.submit(() -> { … });
}
}

10.3 什么时候注意 ThreadLocal

// 线程池里的线程是复用的
// 如果你用了 ThreadLocal,用完不清除
// 下一个任务用同一个线程时,还能拿到上一个任务的数据!

public void handleRequest() {
try {
// 设置用户信息
UserContext.set(userId);

executor.submit(() -> {
// 这里拿到的可能是别人的 userId!
System.out.println(UserContext.get());
});
} finally {
// ⚠️ 必须清除!
UserContext.clear();
}
}

解决方案:用阿里开源的 transmittable-thread-local 或者在回调里显式传参。


十一、一句话速记

七个参数背下来:核心最大存活时,队工艺拒
核心线程、最大线程、存活时间、时间单位、队列、线程工厂、拒绝策略

执行流程四步走:
核心跑 → 队排队 → 扩到最大 → 拒了

参数设多少?
CPU密集 = CPU核数+1
IO密集 = CPU核数×2 起步

别用Executors,手写TPE
队列用有界,工厂要命名
拒绝CallerRuns,异常别丢掉

赞(0)
未经允许不得转载:171主机测评 » 让小白一眼爱上的Java 线程池入门与面试
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址