返回首页面试题

面试必备:线程池核心参数与工作原理

2026年03月25日8 min read

面试必备:线程池核心参数与工作原理

为什么需要线程池?

不用线程池的问题

// 每来一个任务创建一个线程
public void badApproach(Runnable task) {
    new Thread(task).start();  // 问题:无限创建线程,耗尽资源
}

使用线程池的好处

不用线程池:              用线程池:
用户请求 ──→ 新建线程A     用户请求 ──→ 从池取线程 ──→ 执行 ──→ 归还线程
用户请求 ──→ 新建线程B     用户请求 ──→ 从池取线程 ──→ 执行 ──→ 归还线程
用户请求 ──→ 新建线程C     用户请求 ──→ 从池取线程 ──→ 执行 ──→ 归还线程
...无限制...               ...复用固定数量...

优势

  1. 复用线程,减少开销
  2. 控制并发数量,防止资源耗尽
  3. 统一管理,任务队列缓冲

七大核心参数

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

图解线程池工作原理

                    ┌─────────────────────────────────────────┐
                    │              线程池                     │
                    │                                         │
  新任务到来 ────────┼─────────┐                               │
                    │         │                               │
                    │         ▼                               │
                    │  ┌──────────────────┐                   │
                    │  │  线程数 < core?   │                   │
                    │  └────────┬─────────┘                   │
                    │           │                              │
                    │     ┌─────┴─────┐                        │
                    │     │  是        │ 否                    │
                    │     ▼           ▼                        │
                    │ ┌────────┐ ┌──────────────┐             │
                    │ │创建核心 │ │ 队列满?      │             │
                    │ │线程执行 │ └──────┬───────┘             │
                    │ └────────┘        │                      │
                    │            ┌──────┴──────┐                │
                    │            │ 是           │ 否             │
                    │            ▼              ▼               │
                    │     ┌────────────┐  ┌─────────────┐       │
                    │     │ 线程数 <    │  │  拒绝策略    │       │
                    │     │ max?       │  │             │       │
                    │     └─────┬──────┘  └─────────────┘       │
                    │           │                              │
                    │           ▼                              │
                    │    ┌────────────┐                        │
                    │    │创建临时线程│                        │
                    │    │执行任务    │                        │
                    │    └────────────┘                        │
                    │                                         │
                    │  keepAliveTime 后释放临时线程             │
                    │                                         │
                    └─────────────────────────────────────────┘

参数详解

参数说明示例
corePoolSize核心线程数,一直存在5
maximumPoolSize最大线程数10
keepAliveTime空闲线程存活时间60
unit时间单位TimeUnit.SECONDS
workQueue任务队列new LinkedBlockingQueue(100)
threadFactory线程工厂(可自定义线程名)Executors.defaultThreadFactory()
handler拒绝策略new ThreadPoolExecutor.AbortPolicy()

拒绝策略

当线程池和队列都满了,无法处理新任务时的处理方式。

四种内置策略

// ① 抛出异常(默认)
new ThreadPoolExecutor.AbortPolicy()
// 抛出 RejectedExecutionException

// ② 由调用线程执行
new ThreadPoolExecutor.CallerRunsPolicy()
// 让提交任务的线程自己执行

// ③ 丢弃任务,不抛异常
new ThreadPoolExecutor.DiscardPolicy()
// 静默丢弃,不通知

// ④ 丢弃最老的任务
new ThreadPoolExecutor.DiscardOldestPolicy()
// 丢弃队列中最老的任务,然后重试

自定义拒绝策略

// 实现 RejectedExecutionHandler
RejectedExecutionHandler handler = (r, executor) -> {
    // 自定义处理:记录日志、写入文件、发告警
    log.error("线程池满了,拒绝任务: {}", r);
    throw new RejectedExecutionException("线程池已满");
};

常见线程池类型

1. FixedThreadPool(固定大小)

// 核心=最大,队列无界
ExecutorService pool = Executors.newFixedThreadPool(10);

// 内部实现
new ThreadPoolExecutor(
    10, 10, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>()
);

特点:线程数固定,无临时线程

问题:队列无限大,可能 OOM

2. CachedThreadPool(缓存线程池)

// 核心=0,最大=Integer.MAX,队列SynchronousQueue
ExecutorService pool = Executors.newCachedThreadPool();

// 内部实现
new ThreadPoolExecutor(
    0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>()
);

特点

  • 有新任务就创建临时线程
  • 60 秒没任务自动回收
  • 适合短任务场景

问题:线程数无限,可能 OOM

3. SingleThreadExecutor(单线程池)

// 核心=最大=1,队列无界
ExecutorService pool = Executors.newSingleThreadExecutor();

// 内部实现
new ThreadPoolExecutor(
    1, 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>()
);

特点:只有一个线程,任务串行执行

4. ScheduledThreadPool(定时线程池)

// 定时任务
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

// 延迟 3 秒执行
pool.schedule(() -> System.out.println("延迟任务"), 3, TimeUnit.SECONDS);

// 延迟 1 秒,每 2 秒执行一次
pool.scheduleAtFixedRate(() -> System.out.println("周期任务"), 1, 2, TimeUnit.SECONDS);

为什么不推荐 Executors 创建线程池?

// 阿里巴巴规范不允许
ExecutorService pool = Executors.newCachedThreadPool();
ExecutorService pool = Executors.newFixedThreadPool(10);

// 推荐手动创建
new ThreadPoolExecutor(
    corePoolSize, maximumPoolSize,
    keepAliveTime, unit, workQueue,
    threadFactory, handler
);

原因

  • FixedThreadPool/CachedThreadPool 用无界队列,可能 OOM
  • CachedThreadPool 线程数无上限,可能 OOM

最佳实践

如何设置线程数?

// CPU 密集型:core = CPU核数 + 1
int cores = Runtime.getRuntime().availableProcessors();
ExecutorService pool = new ThreadPoolExecutor(
    cores + 1, cores + 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(100)
);

// IO 密集型:core = CPU核数 * 2(或根据 IO 耗时比例计算)
int cores = Runtime.getRuntime().availableProcessors();
ExecutorService pool = new ThreadPoolExecutor(
    cores * 2, cores * 2, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);

// 公式:线程数 = CPU核数 * (1 + IO耗时/CPU耗时)

完整示例

public class ThreadPoolConfig {
    
    public static ExecutorService createPool() {
        int cores = Runtime.getRuntime().availableProcessors();
        
        return new ThreadPoolExecutor(
            cores,                      // corePoolSize
            cores * 2,                  // maximumPoolSize
            60L,                        // keepAliveTime
            TimeUnit.SECONDS,           // unit
            new LinkedBlockingQueue<>(200),  // 队列有界,防止 OOM
            new ThreadFactory() {       // 自定义线程名
                private final AtomicInteger i = new AtomicInteger(1);
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "pool-thread-" + i.getAndIncrement());
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略:调用者执行
        );
    }
}

面试高频问题

Q1:execute 和 submit 的区别?

// execute:提交 Runnable,无返回值
pool.execute(() -> System.out.println("任务"));

// submit:提交 Runnable/Callable,有返回值
Future<String> future = pool.submit(() -> "结果");
String result = future.get();  // 阻塞获取结果

Q2:线程池的拒绝策略在什么时机触发?

  1. 线程池调用了 shutdown()
  2. 线程池达到饱和(队列满 + 线程数达到最大)

Q3:keepAliveTime 的作用?

当线程数 > corePoolSize 时,空闲线程最多存活 keepAliveTime 时间后被回收。

// 核心线程 5,最大 10,空闲 60 秒后回收临时线程
new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, ...);

Q4:synchronized 和线程池的区别?

维度synchronized线程池
作用同步一段代码管理多个任务
线程每次 new复用已有线程
目的保证原子性、可见性控制并发数量
关系可以配合使用独立使用

总结

线程池参数:
├── corePoolSize:核心线程数
├── maximumPoolSize:最大线程数
├── keepAliveTime:临时线程存活时间
├── workQueue:任务队列
├── threadFactory:线程工厂
└── handler:拒绝策略

工作流程:
新任务 → 核心线程执行 → 队列缓冲 → 临时线程执行 → 拒绝策略

拒绝策略:
├── AbortPolicy:抛异常
├── CallerRunsPolicy:调用者执行
├── DiscardPolicy:丢弃
└── DiscardOldestPolicy:丢弃最老

评论区