Java 线程池详解

Java 线程池详解

1. 一句话总结:为什么要用线程池?

线程是稀缺资源,如果无限制地创建,不仅会消耗系统内存,还会导致 CPU 过度切换。

线程池的核心作用:

  1. 降低资源消耗:线程可复用,减少频繁创建/销毁。
  2. 提高响应速度:任务来了直接复用线程执行。
  3. 提高可管理性:统一控制并发度、队列容量、拒绝策略,便于监控和限流。

2. 生活化比喻:线程池就是一个"银行网点"

  • corePoolSize:正式窗口(常驻员工)
  • maximumPoolSize:最多能开的窗口数(正式 + 临时)
  • workQueue:大厅等候座位
  • keepAliveTime:临时窗口空闲多久撤掉
  • handler:保安(满了怎么处理新客人)

3. 核心参数(ThreadPoolExecutor 7 大金刚)

参数 含义 通俗解释
corePoolSize 核心线程数 常驻窗口数(默认不回收)
maximumPoolSize 最大线程数 最大窗口数(含临时加班窗口)
keepAliveTime 存活时间 临时窗口空闲多久就撤掉
unit 时间单位 秒/毫秒/分钟等
workQueue 任务队列 排队区的座位(阻塞队列)
threadFactory 线程工厂 线程怎么创建(命名、优先级、是否守护线程)
handler 拒绝策略 忙不过来时的兜底策略

4. 任务提交后的执行流程(面试必考)

新任务进来后,线程池基本按这个顺序处理(记住这 4 步就够回答 80% 问题):

4.1 流程图解

text 复制代码
       [ 任务提交 ]
          |
          v
+-----------------------+   是   +----------------------+
| 运行线程 < coreSize?  |------>|  创建【核心线程】执行  |
+-----------------------+       +----------------------+
          | 否
          v
+-----------------------+   是   +----------------------+
|  workQueue 未满?      |------>|    任务进入【队列】    |
+-----------------------+       +----------------------+
          | 否
          v
+-----------------------+   是   +----------------------+
| 运行线程 < maxSize?   |------>|  创建【非核心线程】执行 |
+-----------------------+       +----------------------+
          | 否
          v
+-----------------------+
|   触发【拒绝策略】     |  <-- 保安拦截 (Abort/CallerRuns/Discard...)
+-----------------------+

4.2 详细步骤说明(重点:核心线程优先于队列)

  1. 第一步:判断核心线程池 。如果当前运行的线程数 < corePoolSize,则直接创建核心线程执行任务。(注意:即使此时队列没满,也会优先创建核心线程)
  2. 第二步:判断任务队列 。如果核心线程已满,则尝试将任务放入 workQueue 排队。
  3. 第三步:判断最大线程池 。如果队列也满了,且当前运行线程数 < maximumPoolSize,则创建非核心线程(临时工)执行任务。
  4. 第四步:触发拒绝策略 。如果线程数已达到 maximumPoolSize 且队列已满,则执行 handler 拒绝任务。

面试官常问:为什么不先放队列,再开核心线程?

底层设计逻辑 :核心线程被视为"常驻效率单元"。为了保证任务能以最快速度响应,Java 线程池的设计是先尽量让核心线程跑起来,只有核心线程都忙不过来了,才让任务去排队。如果先排队,任务就必须等待线程调度,响应速度会变慢。


5. 四种常见拒绝策略

策略 行为 适用场景
AbortPolicy(默认) 直接抛 RejectedExecutionException 必须快速失败、报警
CallerRunsPolicy 调用者线程自己执行 不能丢任务,允许变慢(削峰)
DiscardPolicy 直接丢弃,不抛异常 可容忍丢弃(如统计、非关键日志)
DiscardOldestPolicy 丢最旧的任务,尝试提交新任务 更关注最新任务

6. 创建线程池的两种方式:Executors vs ThreadPoolExecutor(重点对比)

线程池的创建主要有两种方式:

  1. 通过 Executors 工厂类快速创建(写起来省事,但隐藏默认参数)
  2. 通过 ThreadPoolExecutor 手动配置(参数可控,生产更推荐)

6.1 Executors:快速创建(适合学习/小工具/对稳定性要求不高的场景)

常见工厂方法:

  • Executors.newFixedThreadPool(n):固定大小线程池
  • Executors.newSingleThreadExecutor():单线程池(保证顺序执行)
  • Executors.newCachedThreadPool():缓存线程池(任务来了就扩容)
  • Executors.newScheduledThreadPool(n):定时/周期任务线程池

优点

  • 一行代码就能用,上手快。

缺点(面试必背)

  • 很多工厂方法的默认参数是不受控 的,容易在高并发时把系统拖死:
    • newFixedThreadPool / newSingleThreadExecutor:默认使用无界队列 (任务无限堆积)→ 可能 OOM
    • newCachedThreadPool:最大线程数几乎无限(任务暴增会疯狂建线程)→ 可能 CPU 飙高 / OOM

6.2 ThreadPoolExecutor:手动配置(生产推荐)

你需要显式指定核心参数:核心线程数、最大线程数、队列容量、拒绝策略等。

优点

  • 可控:能明确限制"最多开多少线程""最多排多少队",更稳定。
  • 可观测:可配合线程命名、埋点、监控来定位问题。
  • 可降级:可以按业务选择拒绝策略(抛异常/调用者执行/丢弃等)。

缺点

  • 写起来比 Executors 繁琐,需要你理解参数含义。

6.3 两者对比表(建议面试直接念)

维度 Executors 工厂类 ThreadPoolExecutor 手动配置
上手成本 低(创建快) 中(要理解 7 个参数)
参数可控性 低(默认值隐藏) 高(每个参数可控)
生产安全性 低(常见 OOM/CPU 飙高隐患) 高(能限制队列/线程数)
监控/排障 一般 更好(可自定义线程名、策略)
推荐程度 学习/小工具可用 生产强烈推荐

一句话结论

  • Executors 适合"快速写个能跑的 demo"。
  • ThreadPoolExecutor 适合"线上要扛住流量、要稳定"。

7. 参数怎么设置更合理?(经验法则)

  • CPU 密集型:CPU 核心数CPU 核心数 + 1
  • IO 密集型:CPU 核心数 * 2(更精确需要压测/结合等待时间)

更多代码示例(面试/实战常用)

覆盖:线程命名、不同队列、拒绝策略、submit/Future、定时任务、优雅关闭、异常处理、beforeExecute/afterExecute 钩子、并发工具配合。


示例 1:最常用的 ThreadPoolExecutor(有界队列 + 命名线程 + CallerRuns)

java 复制代码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo1_Basic {
    public static void main(String[] args) {
        AtomicInteger idx = new AtomicInteger(1);
        ThreadFactory tf = r -> {
            Thread t = new Thread(r);
            t.setName("biz-pool-" + idx.getAndIncrement());
            t.setDaemon(false);
            return t;
        };

        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                5,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                tf,
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        for (int i = 0; i < 30; i++) {
            final int taskId = i;
            pool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " handling task " + taskId);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        pool.shutdown();
    }
}

示例 2:对比三种常见队列

2.1 ArrayBlockingQueue(数组有界队列)

java 复制代码
BlockingQueue<Runnable> q = new ArrayBlockingQueue<>(100);

2.2 LinkedBlockingQueue(链表队列,可有界/无界)

java 复制代码
BlockingQueue<Runnable> q = new LinkedBlockingQueue<>(1000); // 建议显式给容量

2.3 SynchronousQueue(直接交接,不存储元素)

java 复制代码
BlockingQueue<Runnable> q = new SynchronousQueue<>();

示例 3:快速演示 4 种拒绝策略的效果

java 复制代码
import java.util.concurrent.*;

public class Demo3_RejectedHandlers {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,
                1,
                0,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() // 换成 CallerRuns/Discard/DiscardOldest 观察不同效果
        );

        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            try {
                pool.execute(() -> {
                    System.out.println("run " + taskId);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            } catch (RejectedExecutionException ex) {
                System.out.println("rejected: " + taskId + ", reason=" + ex);
            }
        }

        pool.shutdown();
    }
}

示例 4:execute() vs submit() + Future 获取结果/异常

java 复制代码
import java.util.concurrent.*;

public class Demo4_SubmitFuture {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        pool.execute(() -> {
            throw new RuntimeException("boom from execute");
        });

        Future<Integer> f = pool.submit(() -> {
            throw new IllegalStateException("boom from submit");
        });

        try {
            System.out.println("result=" + f.get());
        } catch (Exception e) {
            System.out.println("caught from future: " + e);
        }

        pool.shutdown();
    }
}

示例 5:Callable 批量执行 invokeAll

java 复制代码
import java.util.*;
import java.util.concurrent.*;

public class Demo5_InvokeAll {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(4);

        List<Callable<Integer>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            final int x = i;
            tasks.add(() -> x * x);
        }

        List<Future<Integer>> futures = pool.invokeAll(tasks);
        for (Future<Integer> f : futures) {
            System.out.println(f.get());
        }

        pool.shutdown();
    }
}

示例 6:定时线程池(ScheduledExecutorService)

java 复制代码
import java.util.concurrent.*;

public class Demo6_Scheduled {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

        scheduler.schedule(() -> System.out.println("delay once"), 1, TimeUnit.SECONDS);

        scheduler.scheduleAtFixedRate(() -> System.out.println("fixedRate"), 0, 2, TimeUnit.SECONDS);

        scheduler.scheduleWithFixedDelay(() -> {
            System.out.println("fixedDelay start");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, 0, 2, TimeUnit.SECONDS);

        Thread.sleep(7000);
        scheduler.shutdown();
    }
}

示例 7:优雅关闭(shutdown + awaitTermination + shutdownNow)

java 复制代码
import java.util.concurrent.*;

public class Demo7_GracefulShutdown {
    public static void main(String[] args) throws Exception {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2, 4, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100)
        );

        for (int i = 0; i < 20; i++) {
            pool.execute(() -> {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        pool.shutdown();
        if (!pool.awaitTermination(2, TimeUnit.SECONDS)) {
            pool.shutdownNow();
        }

        System.out.println("terminated=" + pool.isTerminated());
    }
}

示例 8:扩展 ThreadPoolExecutor(beforeExecute/afterExecute 统计耗时)

java 复制代码
import java.util.concurrent.*;

public class Demo8_Hooks {
    static class TimingThreadPool extends ThreadPoolExecutor {
        public TimingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                                TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }

        private final ThreadLocal<Long> startTime = new ThreadLocal<>();

        @Override
        protected void beforeExecute(Thread t, Runnable r) {
            startTime.set(System.nanoTime());
        }

        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            long costMs = (System.nanoTime() - startTime.get()) / 1_000_000;
            System.out.println("task cost: " + costMs + "ms" + (t != null ? ", ex=" + t : ""));
        }

        @Override
        protected void terminated() {
            System.out.println("pool terminated");
        }
    }

    public static void main(String[] args) {
        TimingThreadPool pool = new TimingThreadPool(2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));

        pool.execute(() -> {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        pool.shutdown();
    }
}

示例 9:配合 CountDownLatch 等待一批任务完成

java 复制代码
import java.util.concurrent.*;

public class Demo9_CountDownLatch {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(4);
        CountDownLatch latch = new CountDownLatch(10);

        for (int i = 0; i < 10; i++) {
            pool.execute(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        System.out.println("all done");
        pool.shutdown();
    }
}

8.(回到面试)常见追问要点

  1. 线程池里的线程异常了会怎样?

    • 任务抛出运行时异常,执行该任务的线程可能终止;线程池会按需补充新线程以维持池规模(细节与实现/配置有关)。
  2. submit()execute() 区别?

    • execute():不返回结果;异常通常直接打印到工作线程。
    • submit():返回 Future;异常会封装进 Future.get()
  3. RunnableCallable 区别?

    • Runnable 无返回值。
    • Callable 有返回值且可抛异常,常配合 Future 使用。
相关推荐
廋到被风吹走2 小时前
安全防护深度解析:敏感信息加密、密码哈希与密钥管理实战
java
biyezuopinvip2 小时前
基于Spring Boot的投资理财系统设计与实现(毕业论文)
java·spring boot·vue·毕业设计·论文·毕业论文·投资理财系统设计与实现
iAkuya2 小时前
(leetcode)力扣100 75前K个高频元素(堆)
java·算法·leetcode
极客先躯2 小时前
高级java每日一道面试题-2025年7月17日-基础篇[LangChain4j]-如何实现模型的负载均衡和故障转移?
java·langchain·负载均衡·重试机制·负载均衡实现·故障转移实现·多级降级
何中应2 小时前
使用jvisualvm提示“内存不足”
java·jvm·后端
何中应2 小时前
如何手动生成一个JVM内存溢出文件
java·jvm·后端
小灵吖2 小时前
LangChain4j Tool(Function Call)
java·后端
Lxinccode2 小时前
AI编程(3) / claude code[3] : 更新apiKey
java·数据库·ai编程·claude code