【java】线程与线程池
·
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++;
}
如果两个线程同时执行:
- 线程A读取 count = 0
- 线程B读取 count = 0
- A写入1
- 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 | 拒绝策略 |
七、线程池执行流程
- 任务提交
- 线程数 < core → 创建新线程
- 否则进入队列
- 队列满 → 创建非核心线程
- 超过 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核心数
十二、总结
线程核心:
共享内存 → 产生并发问题
线程池核心:
控制并发 + 复用线程 + 限流保护
更多推荐



所有评论(0)