Java线程与线程池详解(原理 + 实战 + 常见问题)

在后端开发中,线程和线程池是高频核心知识点。

如果你:

  • 用过 @Async
  • 配置过线程池
  • 遇到过接口卡死
  • 排查过服务响应慢
  • 遇到过线程池耗尽

那么这篇文章你一定要系统读一遍。


一、线程基础概念

1️⃣ 什么是进程?

进程(Process):

正在运行的应用程序实例。

例如:

  • 启动一个 Spring Boot 应用 = 一个进程
  • 打开一个浏览器 = 一个进程

2️⃣ 什么是线程?

线程(Thread):

程序执行流的最小调度单位。

可以理解为:

  • 进程 = 一个工厂
  • 线程 = 工厂里的工人

一个进程可以包含多个线程。


3️⃣ 线程和进程的关系

对比 进程 线程
资源 独立内存空间 共享进程内存
开销
是否可并发
是否共享数据 不共享 共享

⚠ 重点:

线程之间共享内存 → 容易产生线程安全问题


二、线程生命周期

Java 线程并不是简单的“就绪 / 运行 / 阻塞 / 死亡”。

实际上完整生命周期包括:

NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

1️⃣ 创建线程

Thread t = new Thread(() -> {
    System.out.println("执行任务");
});

状态:

NEW

2️⃣ 调用 start()

t.start();

状态变为:

RUNNABLE

⚠ 注意:

调用 start() 才是真正启动线程
直接调用 run() 只是普通方法调用


3️⃣ 进入阻塞 / 等待状态

线程可能因为以下情况进入非运行状态:

(1)sleep()
Thread.sleep(1000);

进入:

TIMED_WAITING

(2)等待锁
synchronized(obj) {
}

获取不到锁时:

BLOCKED

(3)wait()
obj.wait();

进入:

WAITING

4️⃣ 线程结束

当 run() 方法执行完成:

TERMINATED

三、线程为什么会阻塞?

核心原因:

CPU 资源宝贵,不能被长时间占用。

当线程:

  • 等待 IO
  • 等待锁
  • 等待外部响应
  • 主动 sleep

CPU 会将执行权让出。


四、线程安全问题是如何产生的?

线程安全问题的本质:

多个线程同时修改共享资源。

示例:

private int count = 0;

public void add() {
    count++;
}

如果两个线程同时执行:

  1. 线程A读取 count = 0
  2. 线程B读取 count = 0
  3. A写入1
  4. B写入1

结果:

count = 1

而不是 2。


解决方式

  • synchronized
  • ReentrantLock
  • AtomicInteger
  • CAS
  • volatile

五、线程池

1️⃣ 为什么需要线程池?

错误示例:

for (int i = 0; i < 1000; i++) {
    new Thread(() -> doTask()).start();
}

问题:

  • 创建 1000 个线程
  • 频繁创建销毁
  • 上下文切换开销巨大
  • 可能 OOM

正确方式:

ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 1000; i++) {
    executor.execute(() -> doTask());
}

优点:

✔ 线程复用
✔ 控制并发数量
✔ 提高性能
✔ 降低资源消耗


六、ThreadPoolExecutor 核心参数

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    unit,
    workQueue,
    threadFactory,
    handler
);

参数详解

参数 作用
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 非核心线程存活时间
workQueue 任务队列
threadFactory 线程创建方式
handler 拒绝策略

七、线程池执行流程

  1. 任务提交
  2. 线程数 < core → 创建新线程
  3. 否则进入队列
  4. 队列满 → 创建非核心线程
  5. 超过 maximum → 触发拒绝策略

八、什么是线程池耗尽?

线程池耗尽指:

运行线程数 = maximumPoolSize
任务队列已满
新任务被拒绝

常见异常:

RejectedExecutionException

九、线程池耗尽常见原因

1️⃣ 任务执行时间过长

while(true){}

线程永不释放。


2️⃣ 任务阻塞

Thread.sleep(100000);

3️⃣ 等待外部资源

  • 数据库连接池耗尽
  • HTTP 接口慢
  • RPC 调用阻塞

4️⃣ 参数配置不合理

new ThreadPoolExecutor(
    2,
    2,
    60,
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1)
);

只能同时处理 2 个任务。


十、生产环境建议

🚫 不要使用 Executors 默认线程池
(可能导致 OOM)

推荐:

new ThreadPoolExecutor(...)

建议:

  • IO 密集型 → 核心线程数 = CPU核数 * 2
  • CPU 密集型 → 核心线程数 = CPU核数 + 1
  • 设置合理队列长度
  • 设置拒绝策略
  • 打印线程池监控日志

十一、常见面试问题

1️⃣ 核心线程和最大线程区别?

核心线程常驻
最大线程用于应对高峰


2️⃣ 拒绝策略有哪些?

  • AbortPolicy(默认)
  • CallerRunsPolicy
  • DiscardPolicy
  • DiscardOldestPolicy

3️⃣ 线程池参数如何设计?

看:

  • 任务类型
  • QPS
  • IO耗时
  • CPU核心数

十二、总结

线程核心:

共享内存 → 产生并发问题

线程池核心:

控制并发 + 复用线程 + 限流保护

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐