Java 线程核心概念与生命周期详解
状态名称核心含义NEW(新建)线程对象已创建,但未调用start()方法,未与操作系统底层线程关联RUNNABLE(可运行)调用start()后,线程处于“就绪”或“运行”状态:- 就绪:等待 CPU 调度- 运行:正在占用 CPU 执行任务BLOCKED(阻塞)线程因竞争同步锁(如)被阻塞,等待锁释放WAITING(等待)线程通过wait()join()进入无时限等待,需其他线程唤醒TIMED_
Java 线程核心概念与生命周期详解
一、进程与线程的核心区别
在操作系统和 Java 程序运行中,进程与线程是实现任务执行的基础单元,但二者在资源占用、调度效率等方面存在本质差异,具体对比如下:
| 对比维度 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 资源占用 | 独立的内存空间(代码段、数据段、堆、栈),资源消耗高 | 共享所属进程的内存空间(堆、方法区),仅拥有独立栈和程序计数器,资源消耗低 |
| 调度单位 | 操作系统调度的基本单位(粒度粗) | CPU 调度的基本单位(粒度细,调度效率更高) |
| 通信复杂度 | 需通过进程间通信(IPC,如管道、Socket),复杂度高 | 可通过共享内存(如静态变量、共享对象)直接通信,复杂度低 |
| 独立性 | 进程间相互独立,一个进程崩溃不影响其他进程 | 线程依赖于进程,一个线程崩溃可能导致整个进程崩溃 |
| 创建与销毁成本 | 成本高(需分配独立内存、初始化资源) | 成本低(复用进程资源,仅需初始化栈和程序计数器) |
Java 中的体现:一个 Java 程序启动后,默认对应一个进程(如 java -jar xxx.jar 启动的进程),进程内至少包含一个主线程(main 线程),开发者可通过代码创建多个子线程,实现任务并行执行。
二、并行与串行的区别
并行与串行是任务执行的两种核心模式,直接影响程序的执行效率,尤其在多线程场景中至关重要:
| 对比维度 | 串行(Serial) | 并行(Parallel) |
|---|---|---|
| 执行方式 | 多个任务按顺序执行,一个任务完成后再执行下一个 | 多个任务在同一时间内同时执行(需多核 CPU 支持) |
| 执行效率 | 效率低,总耗时 = 所有任务耗时之和 | 效率高,总耗时 ≈ 耗时最长的单个任务耗时(理想情况) |
| CPU 利用 | 仅占用单个 CPU 核心,多核 CPU 资源无法充分利用 | 可占用多个 CPU 核心,充分利用多核资源 |
| 适用场景 | 任务间存在强依赖(如“先读取文件,再解析文件”) | 任务间无依赖或弱依赖(如“同时下载多个文件”) |
Java 中的示例:
- 串行:
main线程中依次执行“读取数据库数据 → 处理数据 → 写入文件”,三个步骤按顺序完成。 - 并行:创建 3 个线程,分别执行“读取数据库数据”“处理数据”“写入文件”(若任务间无依赖),或创建 10 个线程同时处理 10 批独立数据,利用多核 CPU 加速执行。
三、Java 实现线程的 4 种方式
Java 提供了多种创建线程的途径,不同方式适用于不同场景,核心区别在于“是否继承 Thread 类”和“是否实现任务逻辑与线程控制分离”:
1. 继承 Thread 类
通过继承 java.lang.Thread 类,重写 run() 方法定义任务逻辑,调用 start() 方法启动线程(start() 会触发 JVM 调用 run(),而非直接调用 run())。
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的任务逻辑
System.out.println("线程1执行:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程,触发 run() 执行
}
}
缺点:Java 单继承机制限制,继承 Thread 后无法再继承其他类。
2. 实现 Runnable 接口
实现 java.lang.Runnable 接口的 run() 方法,将任务逻辑与线程控制分离,通过 Thread 类包装后启动。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程2执行:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
// 将 Runnable 任务包装到 Thread 中
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
优点:避免单继承限制,可同时实现其他接口;适合多线程共享同一任务实例(如共享计数器)。
3. 实现 Callable 接口(带返回值)
java.util.concurrent.Callable 接口与 Runnable 类似,但 call() 方法可返回结果且能抛出异常,需配合 FutureTask 获取结果(FutureTask 实现了 RunnableFuture 接口,可作为 Thread 的构造参数)。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 模拟计算任务,返回结果
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws Exception {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// 获取任务结果(若任务未完成,get() 会阻塞)
Integer result = futureTask.get();
System.out.println("计算结果:" + result); // 输出 5050
}
}
适用场景:需要获取线程执行结果的场景(如异步计算、多任务汇总结果)。
4. 使用线程池(推荐)
通过 java.util.concurrent.ExecutorService 线程池创建线程,无需手动管理线程的创建与销毁,降低资源消耗。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建固定大小的线程池(3个线程)
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交任务(Runnable 或 Callable)
for (int i = 1; i <= 5; i++) {
int taskId = i;
executorService.submit(() -> {
System.out.println("线程池任务" + taskId + "执行:" + Thread.currentThread().getName());
});
}
// 关闭线程池(先拒绝新任务,再等待已提交任务完成)
executorService.shutdown();
}
}
优点:复用线程、控制并发数、避免线程频繁创建销毁的开销,是生产环境的首选方式。
四、Java 线程的生命周期
根据 JDK 官方定义,Java 线程的生命周期包含 6 个状态,状态转换通过 Thread.State 枚举类定义,具体如下:
1. 状态定义(Thread.State)
| 状态名称 | 核心含义 |
|---|---|
| NEW(新建) | 线程对象已创建,但未调用 start() 方法,未与操作系统底层线程关联 |
| RUNNABLE(可运行) | 调用 start() 后,线程处于“就绪”或“运行”状态:- 就绪:等待 CPU 调度 - 运行:正在占用 CPU 执行任务 |
| BLOCKED(阻塞) | 线程因竞争同步锁(如 synchronized)被阻塞,等待锁释放 |
| WAITING(等待) | 线程通过 wait()、join()、LockSupport.park() 进入无时限等待,需其他线程唤醒 |
| TIMED_WAITING(计时等待) | 线程通过 sleep(long)、wait(long)、join(long) 进入有时限等待,超时后自动唤醒 |
| TERMINATED(终止) | 线程执行完 run() 方法或因异常退出,生命周期结束,不可再启动 |
2. 状态转换流程图
NEW → RUNNABLE:调用 thread.start()
RUNNABLE → BLOCKED:竞争 synchronized 锁失败
BLOCKED → RUNNABLE:获取 synchronized 锁
RUNNABLE → WAITING:调用 Object.wait() / Thread.join() / LockSupport.park()
WAITING → RUNNABLE:其他线程调用 Object.notify() / Object.notifyAll() / LockSupport.unpark()
RUNNABLE → TIMED_WAITING:调用 Thread.sleep(long) / Object.wait(long) / Thread.join(long)
TIMED_WAITING → RUNNABLE:超时自动唤醒 或 其他线程唤醒
RUNNABLE → TERMINATED:run() 执行完毕 或 线程抛出未捕获异常
注意:线程一旦进入 TERMINATED 状态,无法通过 start() 再次启动,否则会抛出 IllegalThreadStateException。
五、线程中断
线程中断不是强制杀死线程,而是一种协作式通知机制:
- 给目标线程打一个中断标记(
interrupt flag = true) - 线程自己决定何时、如何响应中断
- Java 没有抢占式中断,一切都是协作
5.1、三个核心方法
| 方法 | 类型 | 作用 | 是否清除中断标记 |
|---|---|---|---|
thread.interrupt() |
实例方法 | 发起中断,给线程设置中断标记 = true | 不清除 |
isInterrupted() |
实例方法 | 查询中断状态 | 不清除(标记保留) |
Thread.interrupted() |
静态方法 | 查询当前线程中断状态 | 自动清除(标记重置为 false) |
5.2、方法详细说明
1. thread.interrupt()
- 作用:中断目标线程
- 本质:只是把线程的中断标记设为 true
- 不会立刻停止线程,线程依然可以继续运行
- 如果线程正在
sleep()/wait()/join(),会抛出InterruptedException,并清除中断标记
2. isInterrupted()
- 实例方法,不清除中断标记
- 用法:
thread.isInterrupted() - 适合:只想查看状态,不想改变状态
3. Thread.interrupted()
- 静态方法,自动清除中断标记
- 调用一次后,中断标记变回 false
- 适合:处理完中断后,希望重置状态
5.3、关键区别
Thread.interrupted() vs isInterrupted()
- 静态 vs 实例
interrupted():静态方法,只能查当前线程isInterrupted():实例方法,可以查任意线程
- 是否清除标记
interrupted():会清除isInterrupted():不会清除
5.4、中断的响应方式
线程收到中断后,有 3 种标准处理方式:
- 捕获
InterruptedException,处理后退出 - 检查中断标记,自行退出
- 不处理,继续运行(不推荐)
5.5、最佳实践代码示例
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.interrupted()) { // 会自动清除标记
// 执行业务逻辑
System.out.println("线程运行中...");
}
System.out.println("线程被中断,正常退出");
});
t.start();
Thread.sleep(1000);
t.interrupt(); // 发起中断
}
}
5.6、最重要的一句话总结
Java 线程中断 = 协作式通知,不是强制停止;interrupt() 设标记,isInterrupted() 查标记,interrupted() 查并清标记。
总结
- 中断是标记,不是强制停止
- 三个方法:发起、查询、查询+清除
- Java 只有协作式中断,没有抢占式中断
更多推荐



所有评论(0)