深入 Java 线程池:从源码原理、生产调优到故障排查全链路指南

在 Java 并发编程体系中,线程池是高并发服务的核心基石。从底层的线程资源复用,到上层的业务吞吐量控制,线程池的设计合理性与配置精准度,直接决定了系统的性能上限与稳定性边界。很多开发者对线程池的理解停留在「七大参数」的表层记忆,却对其底层实现原理、生产环境调优、线上故障排查缺乏体系化的认知。

本文将从 ThreadPoolExecutor 的核心 API 出发,逐层拆解 addWorkerWorker 类、runWorker 等核心源码,再延伸到生产环境的参数选型、动态调参、线程池隔离、故障排查实战,最后整理大厂高频面试题,帮你建立从原理到落地的完整线程池知识体系

一、线程池核心基础:核心 API 与核心概念

线程池的本质是「池化思想」在并发场景的落地:通过复用已创建的线程,减少线程创建与销毁的系统开销,同时通过控制最大并发线程数,避免资源耗尽引发的系统雪崩。

1.1 构造函数:七大核心参数

ThreadPoolExecutor 是 JDK 线程池的标准实现,其完整构造函数包含 7 个核心参数,每一个都直接影响线程池的运行行为:

java 复制代码
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
参数 核心含义 作用说明
corePoolSize 核心线程数 线程池长期保留的线程数量,默认空闲时也不会回收
maximumPoolSize 最大线程数 线程池允许创建的最大线程数,队列满时触发非核心线程创建
keepAliveTime 空闲存活时间 非核心线程空闲超过该时间会被回收;核心线程默认不回收
unit 时间单位 keepAliveTime 的时间单位,如秒、毫秒
workQueue 任务队列 存放待执行任务的阻塞队列,是线程池「削峰填谷」的核心载体
threadFactory 线程工厂 用于创建工作线程,可自定义线程名、优先级、守护线程属性
handler 拒绝策略 线程池达到承载上限时,对新任务的处理策略

JDK 内置了 4 种标准拒绝策略:

  • AbortPolicy:默认策略,直接抛出 RejectedExecutionException 异常
  • CallerRunsPolicy:由提交任务的线程同步执行,实现天然背压
  • DiscardPolicy:直接静默丢弃任务,不抛出异常
  • DiscardOldestPolicy:丢弃队列中最老的任务,重新尝试提交当前任务

1.2 任务提交:execute 与 submit

线程池提供了两种任务提交方式,对应不同的业务场景:

execute 方法 :定义在 Executor 接口中,仅支持提交 Runnable 任务,无返回值,也无法捕获任务执行过程中抛出的异常,适用于异步无结果的场景。

arduino 复制代码
void execute(Runnable command);

submit 方法 :定义在 ExecutorService 接口中,支持提交 RunnableCallable 任务,返回 Future 对象。通过 Future 可以获取任务返回值、取消任务、阻塞等待结果,也能捕获执行异常。

scss 复制代码
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);

二者的底层关联:submit 内部会将任务包装为 FutureTask(同时实现了 RunnableFuture 接口),最终还是调用 execute 方法执行。

1.3 优雅关闭:shutdown 与 shutdownNow

线程池的关闭有两种不同强度的模式,对应不同的停机场景:

  • shutdown() :调用后线程池进入 SHUTDOWN 状态,不再接受新任务,但会继续执行队列中已有的任务,所有任务执行完毕后进入终止状态,属于「温和关闭」,保证已提交任务不丢失。
  • shutdownNow() :调用后线程池进入 STOP 状态,立即停止接收新任务,尝试中断正在执行的任务,并返回队列中未执行的任务列表,属于「强制关闭」,速度快但存在任务丢失风险。

生产环境标准关闭范式:

scss 复制代码
executor.shutdown();
// 等待指定时间,若未终止则强制关闭
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
    executor.shutdownNow();
}

二、源码深度解析:线程池工作原理与核心实现

读懂线程池源码,是从「会用」到「精通」的必经之路。Doug Lea 在 ThreadPoolExecutor 中运用了大量并发优化技巧,每一处设计都有其考量。

2.1 任务提交流程总览

当向线程池提交一个任务时,执行逻辑遵循严格的优先级顺序:

  1. 核心线程阶段 :若当前工作线程数 < corePoolSize,创建新的核心线程执行任务
  2. 入队阶段:核心线程已满,尝试将任务加入阻塞队列
  3. 非核心线程阶段 :队列已满且当前线程数 < maximumPoolSize,创建非核心线程执行任务
  4. 拒绝阶段:达到最大线程数且队列已满,执行拒绝策略

2.2 execute 方法源码与设计细节

scss 复制代码
public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    int c = ctl.get();
    // 1. 工作线程数小于核心线程数,直接创建新线程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2. 线程池处于运行状态,尝试入队
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 双重检查:入队后再次校验线程池状态
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 3. 入队失败,尝试创建非核心线程
    else if (!addWorker(command, false))
        reject(command);
}

这里有两个经典设计:

  • ctl 变量 :一个 AtomicInteger 变量,高 3 位存储线程池状态,低 29 位存储工作线程数量,通过一个变量同时维护两个维度的信息,大幅减少了锁的使用。
  • 双重状态校验:任务入队后再次校验线程池状态,避免入队瞬间线程池被关闭导致任务永久滞留队列。

2.3 addWorker:线程创建的核心逻辑

addWorker 是线程池创建工作线程的核心方法,分为「CAS 增加线程计数」和「创建启动 Worker 线程」两个阶段:

ini 复制代码
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 状态校验:线程池已关闭则不创建新线程
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // CAS自旋增加工作线程计数
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    workers.add(w); // 加入工作线程集合
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start(); // 启动工作线程
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

核心设计思想:高频的计数操作通过 CAS 自旋无锁完成,低频的集合操作通过加锁保证安全,锁细化的设计大幅提升了高并发下的提交性能。

2.4 Worker 类:为什么要继承 AQS

Worker 是线程池的工作执行单元,每个 Worker 对应一个工作线程。它同时继承了 AbstractQueuedSynchronizer 并实现了 Runnable 接口,身兼「任务执行载体」和「状态同步锁」双重角色。

arduino 复制代码
private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    final Thread thread;
    Runnable firstTask;
    volatile long completedTasks;

    Worker(Runnable firstTask) {
        setState(-1); // 初始状态为-1,禁止线程启动前被中断
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    public void run() {
        runWorker(this);
    }
    
    // 不可重入的独占锁实现
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

Worker 不直接使用 ReentrantLock,而是自己实现 AQS,有三个核心原因:

  1. 状态与工作绑定state=0 表示线程空闲,state=1 表示正在执行任务。锁的持有状态和线程工作状态完全绑定,线程池关闭时可以精准识别并中断空闲线程,不会打断正在执行的任务。
  2. 不可重入特性:可重入锁可能在任务执行过程中因重入释放锁,导致 state 临时变为 0,被误判为空闲线程而中断。不可重入设计保证了「持有锁 = 正在工作」的语义绝对成立。
  3. 轻量化设计 :Worker 只需要最简单的独占锁语义,不需要公平锁、条件变量等高级特性,自主实现 AQS 内存开销更小。

而构造函数中 setState(-1) 是一个精妙的边界处理:线程池的中断方法会判断 state >= 0 才执行中断,初始状态 - 1 保证了线程创建后、启动前不会被意外中断,直到 runWorker 中调用 unlock() 将 state 置为 0,线程才正式进入可中断状态。

2.5 runWorker:线程复用的核心循环

runWorker 是工作线程的主生命周期,也是线程池「线程复用」的本质 ------ 一个线程在循环中反复从队列取任务执行,直到取不到任务才退出。

ini 复制代码
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // state从-1置为0,允许被中断
    
    boolean completedAbruptly = true;
    try {
        // 核心循环:有任务执行,无任务从队列拉取
        while (task != null || (task = getTask()) != null) {
            w.lock(); // 加锁,标记线程正在工作

            // 保证STOP状态下线程一定持有中断标记
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();

            try {
                beforeExecute(wt, task); // 执行前钩子
                Throwable thrown = null;
                try {
                    task.run(); // 真正执行任务
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown); // 执行后钩子
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock(); // 解锁,标记线程空闲
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly); // 线程退出清理
    }
}

两个关键细节:

  • 加锁的目的不是保护任务线程安全,而是标记工作状态,供线程池关闭时识别空闲线程。
  • beforeExecuteafterExecute 是空实现的钩子方法,是生产环境实现监控、异常捕获的核心扩展点。

2.6 getTask:线程回收的决策逻辑

getTask 决定了工作线程何时退出,是核心线程与非核心线程差异化回收策略的实现位置:

ini 复制代码
private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 关闭状态校验:满足条件则返回null,线程退出
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);
        // 判断当前线程是否需要超时回收
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 超时回收线程用poll限时等待,核心线程用take永久阻塞
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

核心逻辑:通过 timed 变量区分线程类型,需要回收的线程使用 poll 超时等待,超时未取到任务则退出;核心线程默认使用 take 永久阻塞,持续等待新任务。

三、生产环境配置:参数选型与监控体系

线程池参数没有万能公式,需要结合业务场景压测调优,但有成熟的选型原则可遵循。

3.1 线程数大小的选型公式

  • CPU 密集型任务 :推荐 corePoolSize = CPU核心数 + 1 CPU 密集型任务 CPU 利用率高,过多线程只会增加上下文切换开销;+1 是为了应对线程偶发缺页中断,保证 CPU 利用率最大化。
  • IO 密集型任务 :推荐 corePoolSize = 2 * CPU核心数 IO 密集型任务线程大部分时间处于阻塞等待状态,更多线程可以在等待时切换执行其他任务,提升吞吐量。更精准的公式为:线程数 = CPU核心数 * (1 + 平均等待时间 / 平均计算时间)

3.2 阻塞队列的选型对比

队列类型 核心特点 适用场景
ArrayBlockingQueue 有界数组队列,FIFO,容量固定 流量可控的稳定业务,防止任务无限堆积
LinkedBlockingQueue 链表队列,默认容量为 Integer.MAX_VALUE 任务量平稳的场景;无界配置存在 OOM 风险
SynchronousQueue 不存储任务,直接交付给线程执行 低延迟场景,配合较大的最大线程数使用
PriorityBlockingQueue 优先级无界队列 需要按优先级执行任务的特殊场景

3.3 拒绝策略的选型建议

  • 核心业务、不可丢失的任务:选择 CallerRunsPolicy,通过提交方同步执行实现背压,既不丢失任务,又能降低提交速率。
  • 非核心、可丢弃的任务(如日志上报、统计):选择 DiscardPolicy,队列满时直接丢弃,不影响主链路。
  • 严格保证不丢失、需要感知失败的场景:选择 AbortPolicy,抛出异常由业务方做降级处理。

3.4 线程池监控指标与扩展钩子

线上运行的线程池必须具备完善的可观测性,ThreadPoolExecutor 提供了丰富的原生监控指标:

  • getTaskCount():已提交的任务总数
  • getCompletedTaskCount():已完成的任务数
  • getLargestPoolSize():历史达到的最大线程数
  • getPoolSize():当前工作线程总数
  • getActiveCount():当前正在执行任务的活跃线程数

通过继承 ThreadPoolExecutor 重写 beforeExecuteafterExecuteterminated 三个钩子方法,可以实现任务耗时统计、异常捕获、监控上报等定制化能力。生产级可监控线程池实现示例:

scala 复制代码
public class MonitorableThreadPool extends ThreadPoolExecutor {
    private final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
    private final AtomicLong failTaskCount = new AtomicLong(0);
    private final AtomicLong totalCostTime = new AtomicLong(0);
    private final String poolName;

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        startTimeThreadLocal.set(System.currentTimeMillis());
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        long cost = System.currentTimeMillis() - startTimeThreadLocal.get();
        startTimeThreadLocal.remove();
        totalCostTime.addAndGet(cost);
        if (t != null) {
            failTaskCount.incrementAndGet();
            // 记录异常日志、上报告警
        }
    }
}

四、线上优化实战:动态调参与性能优化

4.1 线上常见问题与根因

  1. 核心线程不足,队列等待过长 现象:接口响应慢,活跃线程数长期等于核心线程数,队列积压严重。根因是核心线程数设置过于保守,流量高峰时任务全部排队,等待时间远超执行时间。
  2. 队列过短,频繁触发拒绝 现象:业务高峰期大量抛出拒绝异常。根因是队列容量设置过小,流量突增时快速打满,直接触发拒绝策略。
  3. 异常导致线程频繁重建 现象:线程数频繁波动,GC 次数偏多。根因是 execute 提交的任务未捕获异常,异常会导致 Worker 线程直接死亡,线程池频繁创建新线程补充。

4.2 线程池动态参数调整实现

ThreadPoolExecutor 原生支持运行时动态修改核心参数,无需重启服务即可应对流量波动,是生产环境弹性调优的基础能力。

arduino 复制代码
public void refreshConfig(int newCoreSize, int newMaxSize, long newKeepAliveTime, TimeUnit unit) {
    // 先改最大线程数,再改核心线程数,避免core > max触发异常
    if (newMaxSize >= newCoreSize) {
        executor.setMaximumPoolSize(newMaxSize);
        executor.setCorePoolSize(newCoreSize);
        executor.setKeepAliveTime(newKeepAliveTime, unit);
    }
}

生产环境通常对接配置中心(Nacos/Apollo)实现参数热更新,大促前提前扩容,低谷期自动缩容,兼顾性能与资源成本。

4.3 经典优化方案:SynchronousQueue + 调用者执行策略

对于短耗时、低延迟要求的场景,推荐如下配置范式:

scss 复制代码
ThreadPoolExecutor fastTaskPool = new ThreadPoolExecutor(
        0,
        Runtime.getRuntime().availableProcessors() * 3,
        30L, TimeUnit.SECONDS,
        new SynchronousQueue<>(),
        new NamedThreadFactory("fast-task-pool"),
        new ThreadPoolExecutor.CallerRunsPolicy()
);

设计思路

  • SynchronousQueue 不缓存任务,任务提交后直接交给线程执行,避免队列等待延迟
  • 核心线程为 0,闲时零资源占用,线程全部可回收
  • CallerRunsPolicy 拒绝策略实现天然背压:任务处理不过来时,提交线程自己执行,自动降低提交速率,既不丢失任务,又能起到流量削峰的效果

五、Executors 框架:便捷背后的风险

5.1 Executor 框架整体结构

java.util.concurrent.Executors 是 JDK 提供的线程池工厂工具类,整个 Executor 框架分为两大组成部分:

  • 工作单元RunnableCallable,代表待执行的任务实体
  • 执行机制 :以 Executor 接口为核心的执行体系,核心实现为 ThreadPoolExecutorScheduledThreadPoolExecutor

5.2 Runnable 与 Callable 的区别与适用场景

对比维度 Runnable Callable
返回值 无返回值 支持泛型返回值
异常处理 无法向上抛出受检异常 可抛出受检异常,通过 Future.get () 获取
提交方式 execute、submit 均可 只能通过 submit 提交
适用场景 异步无结果任务,如日志上报、消息发送 需要执行结果的计算任务、多任务聚合

5.3 为什么不推荐使用 Executors 创建线程池

《阿里巴巴 Java 开发手册》明确禁止使用 Executors 创建线程池,核心原因在于其内置实现存在资源耗尽风险:

  1. FixedThreadPool / SingleThreadPool :使用无界 LinkedBlockingQueue,默认容量为 Integer.MAX_VALUE,任务堆积时极易引发 OOM。
  2. CachedThreadPool :最大线程数设置为 Integer.MAX_VALUE,高并发下会创建大量线程,耗尽系统 CPU 和内存资源。
  3. ScheduledThreadPool:同样使用无界队列,任务堆积风险高,且异常任务会导致调度终止。
  4. 默认线程工厂:生成的线程名无业务含义,线上排查问题难以定位归属。

生产环境最佳实践:通过 ThreadPoolExecutor 构造函数手动创建,明确指定每一个参数,自定义线程工厂设置业务化线程名,根据业务场景选择合适的拒绝策略。

六、线程池隔离:舱壁模式的工程落地

6.1 为什么必须做线程池隔离

如果所有业务共用一个全局线程池,某一个慢任务或故障业务占满线程池时,会导致所有业务全部排队等待,形成级联雪崩。比如短信服务商故障导致短信任务阻塞,最终拖垮整个订单链路,这就是典型的「一损俱损」。

线程池隔离本质是舱壁模式(Bulkhead Pattern) 的落地:将系统资源拆分成多个独立的池,故障影响被限制在自身范围内,保障核心业务的资源配额与可用性。

6.2 三个核心隔离维度

生产环境通常按多个维度组合拆分,核心原则是:风险不同、优先级不同、特性不同的任务,不共用线程池。

  1. 按业务优先级隔离:核心业务池配额充足、策略保守;非核心业务池资源有限,高峰期可降级丢弃。
  2. 按任务耗时隔离:快任务与慢任务拆分,避免慢任务长时间占用线程,导致快任务排队。
  3. 按业务域隔离:按业务领域拆分独立线程池,故障时可快速定位业务线,避免跨业务影响。

6.3 生产级线程池管理器实现

散落在业务代码中创建线程池会导致资源不可控、监控不统一。生产环境推荐通过统一管理器注册和管理所有线程池,实现「统一创建、统一监控、统一销毁」。

arduino 复制代码
public class ThreadPoolManager {
    private final Map<String, MonitorableThreadPool> poolMap = new ConcurrentHashMap<>();
    private static final ThreadPoolManager INSTANCE = new ThreadPoolManager();

    public void registerPool(String poolName, int corePoolSize, int maxPoolSize,
                             long keepAliveTime, TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             RejectedExecutionHandler handler) {
        // 创建并注册可监控线程池
    }

    public MonitorableThreadPool getPool(String poolName) {
        return poolMap.get(poolName);
    }

    public void reportAllMetrics() {
        // 批量上报所有线程池监控指标
    }

    public void shutdownAll() {
        // 服务停机时优雅关闭所有线程池
    }
}

七、线上故障排查:方法论与经典案例

7.1 通用排查四步法与工具清单

遇到线程池相关问题,遵循「看监控 → 抓线程栈 → 定位异常任务 → 验证修复」的四步排查法:

  1. 看监控指标:判断是线程打满、队列积压、拒绝突增还是耗时上涨,快速定位问题类型。
  2. 抓线程栈:通过线程栈查看线程真实状态,定位是锁等待、IO 阻塞还是死循环。
  3. 定位异常任务:结合业务日志,定位是哪一类任务导致的问题,是否有下游故障。
  4. 验证修复:调整参数或修复问题后,持续观察监控指标确认恢复。

常用工具:Arthas(线上排查首选)、jstack、jmap + MAT、监控平台。

7.2 四大经典线上故障复盘

案例 1:核心线程不足 + 队列过长,接口全线超时

现象:大促高峰期接口响应从 100ms 飙升到 2s,大量超时,CPU 利用率仅 30%。

根因:核心线程数设置过于保守,任务处理能力跟不上流量,全部在队列排队,等待时间远超执行时间。

修复:调大核心线程数,缩短队列长度,超过容量直接降级而非长时间等待。

案例 2:无界队列引发 OOM,服务频繁挂掉

现象:低峰期服务频繁 Full GC,最终 OOM 重启。

根因 :使用 Executors.newFixedThreadPool 创建的线程池,队列无界;下游日志服务故障时任务只进不出,最终撑爆内存。

修复:替换为有界队列,非核心任务使用丢弃策略,增加队列积压告警。

案例 3:未做隔离,慢任务拖垮全服务

现象:所有接口响应变慢,吞吐量下降 80%,但 CPU 内存不高。

根因:公共线程池被第三方慢调用占满,核心业务任务全部排队。

修复:按业务优先级和耗时拆分线程池,第三方调用独立池并配置熔断。

案例 4:未捕获异常,线程频繁销毁重建

现象:线程数频繁波动,GC 次数偏多,任务量平稳但线程创建销毁频繁。

根因:execute 提交的任务未捕获异常,异常导致 Worker 线程死亡,线程池反复创建新线程。

修复:任务内层增加异常捕获,重写 afterExecute 统一处理异常。

7.3 排查常用命令速查

ini 复制代码
# Arthas查看线程状态
thread

# jstack抓取线程栈
jstack <pid> > thread_dump.txt

# 查看进程线程总数
ps -T -p <pid> | wc -l

# 导出堆快照分析OOM
jmap -dump:format=b,file=heap_dump.hprof <pid>

八、大厂高频面试题汇总

基础原理篇

  1. 线程池的七大核心参数是什么?分别有什么作用?
  2. 任务提交后线程池的执行流程是什么?
  3. shutdown 和 shutdownNow 有什么区别?
  4. Runnable 和 Callable 有什么区别?Future 和 FutureTask 是什么关系?
  5. 四种内置拒绝策略分别是什么?适用场景有哪些?

源码深度篇

  1. Worker 类为什么要继承 AQS?为什么不直接用 ReentrantLock?
  2. addWorker 方法的执行流程是什么?为什么用 CAS 自旋而不是直接加锁?
  3. runWorker 方法的核心逻辑是什么?beforeExecute 和 afterExecute 有什么作用?
  4. ctl 变量的设计有什么巧妙之处?
  5. 任务执行抛出异常后,线程池会怎么处理?
  6. 核心线程默认不会被回收,如何让核心线程也超时回收?

生产实战篇

  1. 如何设置线程池大小?CPU 密集型和 IO 密集型分别怎么配置?
  2. 为什么不建议使用 Executors 创建线程池?具体有什么风险?
  3. 线上线程池如何监控?重点关注哪些指标?
  4. 生产环境遇到线程池队列积压怎么办?如何排查和优化?
  5. 线上可以动态修改线程池参数吗?如何实现?

进阶拓展篇

  1. 线程池会导致死锁吗?什么场景下会出现?
  2. 什么是线程池隔离?为什么要做线程池隔离?
  3. 如何优雅地关闭线程池?标准范式是什么?
  4. 核心线程数 = CPU 核心数 + 1 这个公式绝对正确吗?为什么?
相关推荐
Conan在掘金1 小时前
ArkTS 进阶之道(30):@Track 精准观测边界——为啥 class 属性级观测只刷关联 UI 根因
后端
明月_清风1 小时前
🚀 OpenAI 数据代理架构全解析:从 600 PB 到自然语言的六层上下文工程
前端·后端·架构
明月_清风1 小时前
🚀 从 Foundry 到 AIP:Palantir 发生了什么变化?一篇文章全搞懂
前端·后端
Zane19942 小时前
闭包到底"闭"住了什么?一文讲透 LEGB 规则与循环里的闭包陷阱
后端·python
techdashen2 小时前
Go设计取舍之六: sync.Mutex正常模式与饥饿模式
开发语言·后端·golang
Zane19942 小时前
CAS 与原子类:Java 如何实现无锁编程
java·后端
颜进强2 小时前
前端看后端 13:什么是 Cookie 和 Session?
前端·后端
gitboyzcf2 小时前
mpegts.js解决Chrome无法播放7568×142分辨率报错 DOMException问题
前端·后端
程序员-Benothing2 小时前
Java ForkJoinPool 详解:从分治思想到高性能并行计算
java·开发语言·后端·面试·职场和发展