2026年7月11日 · 5 分钟阅读
Java 并发编程入门:线程、进程、上下文切换与线程生命周期
梳理 Java 多线程基础,包括进程和线程、并发与并行、线程上下文切换、线程状态,以及 Java 线程到操作系统线程的执行链路。
并发编程是高性能 Java 应用绕不开的一关。Tomcat、MySQL、Redis、Elasticsearch、连接池、线程池,本质上都大量使用了多线程。
学并发,不是为了多写几个 new Thread(),而是为了理解多个任务同时运行时,数据为什么会乱、线程为什么会卡、性能为什么会下降。
什么是多线程
多线程是指一个程序中同时存在多个执行路径。它可以让程序更好地利用 CPU、IO 等资源,提高整体处理能力。
但多线程也带来新问题:
- 多个线程同时修改共享数据。
- 线程之间看不到彼此的修改。
- 代码执行顺序和编写顺序不完全一致。
- 线程创建和切换本身也有成本。
并发编程就是编写多线程代码,并解决多线程带来的正确性和性能问题。
进程和线程
进程是操作系统分配资源的基本单位,可以理解为正在运行的一个应用程序。
线程是进程中的执行单元,一个进程可以包含多个线程。
| 对比项 | 进程 | 线程 |
|---|---|---|
| 内存空间 | 独立 | 同一进程内线程共享堆和方法区 |
| 创建成本 | 较高 | 较低 |
| 通信成本 | 较高,需要 IPC | 较低,可共享进程内数据 |
| 隔离性 | 强 | 弱 |
| 崩溃影响 | 通常影响单个进程 | 一个线程异常可能影响整个进程 |
线程共享堆和方法区,但每个线程都有自己的虚拟机栈、本地方法栈和程序计数器。
并发和并行
并发和并行经常被混用,但它们不是一回事。
| 概念 | 含义 |
|---|---|
| 并发 | 同一时间段内多个任务都在推进,不一定同时执行 |
| 并行 | 同一时刻多个任务真正同时执行 |
并发是一种处理多任务的能力,并行是多核 CPU 提供的同时执行能力。
单核 CPU 也能并发,因为它可以通过时间片快速切换线程;多核 CPU 才能真正让多个线程在同一时刻并行运行。
线程上下文切换
CPU 不会一直执行同一个线程。操作系统会把 CPU 时间片分给不同线程,当线程时间片用完、阻塞或被更高优先级任务抢占时,就会发生上下文切换。
一次线程上下文切换通常包含:
- 保存当前线程的 CPU 寄存器、程序计数器等状态。
- 选择下一个要运行的线程。
- 恢复下一个线程之前保存的上下文。
- 跳转到该线程上次暂停的位置继续执行。
上下文切换不是免费的。线程过多时,CPU 可能把大量时间花在切换上,而不是执行业务代码。
线程越多越好吗
线程不是越多越好。
线程过多会带来几个问题:
- 栈内存占用增加。
- 上下文切换成本升高。
- 锁竞争更激烈。
- 调度变复杂,响应时间抖动。
对于 CPU 密集型任务,线程数通常接近 CPU 核数更合理;对于 IO 密集型任务,可以适当多一些线程,因为线程经常等待 IO。
线程生命周期
Java 线程常见状态包括:
| 状态 | 说明 |
|---|---|
NEW | 线程已创建,但还没有调用 start() |
RUNNABLE | 可运行状态,可能正在运行,也可能等待 CPU 调度 |
BLOCKED | 等待进入同步代码块或同步方法 |
WAITING | 无限期等待其他线程唤醒 |
TIMED_WAITING | 限时等待 |
TERMINATED | 线程执行结束 |
RUNNABLE 不等于一定正在执行。它只是说明线程具备运行资格,具体什么时候运行由操作系统调度。
sleep、wait 和 yield
几个常见方法容易混淆。
| 方法 | 作用 | 是否释放锁 |
|---|---|---|
Thread.sleep() | 当前线程休眠指定时间 | 不释放锁 |
Object.wait() | 当前线程等待其他线程通知 | 释放锁 |
Thread.yield() | 当前线程让出 CPU 调度机会 | 不释放锁 |
Thread.interrupt() | 中断线程,设置中断标记 | 不直接停止线程 |
interrupt() 不是强制杀死线程,而是一种协作式中断机制。线程需要自己检查中断状态并决定如何退出。
Java 线程如何启动
调用 Thread.start() 后,Java 线程不是直接在 Java 层执行 run()。它背后会进入 JVM 的 native 方法,再创建操作系统线程。
可以简化理解为:
Thread.start()
-> native start0()
-> JVM_StartThread
-> 创建 JavaThread
-> 创建 OS 线程
-> OS 线程执行入口
-> 回调 Java run()
这说明线程创建是比较重的操作,涉及 JVM 和操作系统协作。频繁创建线程会影响性能,这也是线程池存在的原因。
线程安全从哪里来
线程安全问题通常来自共享可变状态。
比如多个线程同时卖票:
if (ticket > 0) {
ticket--;
}
这段逻辑看起来简单,但判断和扣减不是一个不可分割的操作。多个线程可能同时看到同一张票还存在,最终导致重复售卖或卖出不存在的票。
要解决线程安全问题,通常有三类思路:
- 避免共享:每个线程使用自己的数据。
- 不可变:共享对象不可修改。
- 同步控制:用锁、原子类、并发容器等保护共享数据。
小结
Java 并发入门要先抓住几个基础判断:
- 进程是资源单位,线程是执行单位。
- 并发不等于并行。
- 线程切换有成本,线程不是越多越好。
- Java 线程最终要映射到操作系统线程。
- 线程安全问题来自共享可变状态。
后续学习 JMM、synchronized、volatile、CAS、AQS 和线程池,都是在回答同一个问题:多个线程怎样正确、高效地协作。