【Java并发编程】线程生命周期、线程创建的4种方式(附《思维导图》+《面试高频考点清单》)
文章目录

Java并发编程:线程生命周期与4种创建方式 系统性知识体系
一、整体知识体系架构图
Java线程核心基础
├─ 线程生命周期
│ ├─ 5种核心状态定义
│ ├─ 状态转换完整流程
│ ├─ 关键方法对状态的影响
│ └─ 易混淆概念辨析
└─ 线程创建的4种方式
├─ 继承Thread类
├─ 实现Runnable接口
├─ 实现Callable接口+FutureTask
└─ 使用线程池(Executor框架)
└─ 4种方式对比与选型建议
二、Java线程生命周期(面试核心考点)
2.1 线程的5种核心状态(JDK定义)
Java线程的状态在java.lang.Thread.State枚举中明确定义,共5种(注意:操作系统层面通常分为7种,不要混淆):
| 状态 | 英文 | 定义 | 核心特征 |
|---|---|---|---|
| 新建 | NEW | 线程对象已创建,但尚未调用start()方法 |
仅存在于JVM堆内存,未与操作系统线程绑定 |
| 可运行 | RUNNABLE | 线程已调用start(),正在JVM中执行或等待CPU调度 |
包含操作系统的"就绪(Ready)"和"运行(Running)"两个子状态 |
| 阻塞 | BLOCKED | 线程因等待锁(synchronized)而被阻塞 | 只能由"等待锁释放"事件唤醒,进入RUNNABLE状态 |
| 等待 | WAITING | 线程因调用wait()、join()、LockSupport.park()而无限期等待 |
必须由其他线程显式唤醒(notify()/notifyAll()/unpark()) |
| 超时等待 | TIMED_WAITING | 线程在指定时间内等待,超时后自动唤醒 | 调用wait(long)、sleep(long)、join(long)等方法 |
| 终止 | TERMINATED | 线程执行完毕或异常退出 | 生命周期结束,无法再次调用start() |
2.2 完整状态转换流程图
新建(NEW)
↓ start()
可运行(RUNNABLE) ←──────────────────────────┐
↓ CPU调度 │
运行中(Running) │
├─ 正常执行完毕 → 终止(TERMINATED) │
├─ 异常退出 → 终止(TERMINATED) │
├─ 调用synchronized未获取锁 → 阻塞(BLOCKED) ─┘
├─ 调用wait() → 等待(WAITING) │
│ ↓ notify()/notifyAll() │
│ └──────────────────────────────────────┘
├─ 调用wait(long) → 超时等待(TIMED_WAITING) ─┐
│ ↓ 超时/notify()/notifyAll() │
│ └──────────────────────────────────────┘
├─ 调用sleep(long) → 超时等待(TIMED_WAITING) ─┘
└─ 调用join() → 等待(WAITING) │
↓ 被join线程执行完毕 │
└──────────────────────────────────────┘
2.3 关键方法对状态的影响(易混淆点)
-
start()vsrun()start():启动线程,将线程从NEW转为RUNNABLE,由JVM调用run()方法run():只是普通方法调用,不会启动新线程,仍在当前线程执行
-
wait()vssleep()(面试必问)对比项 wait() sleep() 所属类 Object类 Thread类 锁释放 释放持有的对象锁 不释放任何锁 调用条件 必须在synchronized代码块中 任何地方都可调用 唤醒方式 其他线程调用notify()/notifyAll()或超时 超时时间到或被interrupt() 用途 线程间通信 线程暂停执行 -
join()- 让当前线程等待调用
join()的线程执行完毕后再继续 - 底层基于
wait()实现,会释放当前线程持有的锁 - 带超时参数的
join(long)会进入TIMED_WAITING状态
- 让当前线程等待调用
-
interrupt()- 不会直接终止线程,只是设置线程的中断标志位
- 对处于WAITING/TIMED_WAITING状态的线程,会抛出InterruptedException并清除中断标志
- 对处于RUNNABLE状态的线程,仅设置标志位,需要线程主动检查
isInterrupted()来响应中断
2.4 常见误区澄清
- ❌ 误区1:线程调用
start()后立即执行
✅ 正确:进入RUNNABLE状态,等待CPU调度器分配时间片 - ❌ 误区2:线程终止后可以再次调用
start()
✅ 正确:会抛出IllegalThreadStateException,一个线程只能启动一次 - ❌ 误区3:
stop()方法可以安全终止线程
✅ 正确:stop()已被废弃,会强制释放所有锁,导致数据不一致,应使用中断机制优雅终止
三、Java线程创建的4种方式
3.1 方式一:继承Thread类
实现步骤:
- 定义类继承
java.lang.Thread - 重写
run()方法,编写线程执行逻辑 - 创建子类实例,调用
start()方法启动线程
代码示例:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
优缺点:
- ✅ 优点:实现简单,直接调用
this即可获取当前线程 - ❌ 缺点:Java单继承限制,无法继承其他类;任务与线程耦合,不利于代码复用
3.2 方式二:实现Runnable接口
实现步骤:
- 定义类实现
java.lang.Runnable接口 - 实现
run()方法,编写线程执行逻辑 - 创建Runnable实现类实例,作为参数传入Thread构造器
- 调用Thread对象的
start()方法启动线程
代码示例:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
thread1.start();
thread2.start();
}
}
优缺点:
- ✅ 优点:避免单继承限制;任务与线程分离,同一个Runnable可以被多个线程执行;符合面向接口编程思想
- ❌ 缺点:无法获取线程执行结果;无法抛出受检异常
3.3 方式三:实现Callable接口+FutureTask
实现步骤:
- 定义类实现
java.util.concurrent.Callable<V>接口 - 实现
call()方法,编写线程执行逻辑并返回结果 - 创建Callable实现类实例,包装成
FutureTask<V>对象 - 将FutureTask对象作为参数传入Thread构造器
- 调用Thread对象的
start()方法启动线程 - 通过
FutureTask.get()方法获取线程执行结果(会阻塞)
代码示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
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 ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// get()方法会阻塞直到线程执行完毕返回结果
Integer result = futureTask.get();
System.out.println("计算结果: " + result);
}
}
优缺点:
- ✅ 优点:可以获取线程执行结果;可以抛出受检异常;支持泛型返回值
- ❌ 缺点:实现相对复杂;
get()方法会阻塞当前线程
3.4 方式四:使用线程池(Executor框架)
实现步骤:
- 使用
java.util.concurrent.Executors工具类创建线程池 - 提交Runnable或Callable任务给线程池执行
- 关闭线程池
代码示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolExample {
public static void main(String[] args) throws Exception {
// 创建固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 提交Runnable任务
threadPool.submit(new MyRunnable());
// 提交Callable任务并获取Future
Future<Integer> future = threadPool.submit(new MyCallable());
System.out.println("Callable结果: " + future.get());
// 关闭线程池
threadPool.shutdown();
}
}
优缺点:
- ✅ 优点:
- 避免频繁创建销毁线程,降低系统开销
- 提高响应速度,任务到达时无需等待线程创建
- 统一管理线程,可控制并发数,避免系统过载
- 提供更丰富的功能:定时执行、周期执行等
- ❌ 缺点:需要合理配置线程池参数,否则可能导致性能问题
3.5 4种创建方式对比与选型建议
| 对比项 | 继承Thread类 | 实现Runnable接口 | 实现Callable接口 | 使用线程池 |
|---|---|---|---|---|
| 单继承限制 | 有 | 无 | 无 | 无 |
| 返回值 | 无 | 无 | 有 | 有 |
| 异常处理 | 只能捕获,不能抛出 | 只能捕获,不能抛出 | 可以抛出受检异常 | 可以抛出受检异常 |
| 资源消耗 | 高(频繁创建销毁) | 高 | 高 | 低(复用线程) |
| 代码耦合度 | 高 | 低 | 低 | 最低 |
| 适用场景 | 简单场景,无需复用 | 多数无返回值场景 | 需要返回值的场景 | 生产环境所有并发场景 |
选型优先级:
- 生产环境优先使用线程池(避免手动创建线程导致的资源耗尽问题)
- 简单无返回值任务使用Runnable接口
- 需要返回值的任务使用Callable+Future
- 尽量避免继承Thread类(除非需要重写Thread的其他方法)
四、核心面试高频问题清单
- 简述Java线程的5种状态及转换条件
wait()和sleep()的区别是什么?start()和run()的区别是什么?- 如何优雅地终止一个线程?
- 为什么不推荐使用
stop()方法终止线程? - Java创建线程有哪几种方式?各有什么优缺点?
- Callable和Runnable的区别是什么?
- 为什么推荐使用线程池而不是手动创建线程?
- 一个线程两次调用
start()方法会发生什么? - 线程的中断机制是如何工作的?
五、知识体系总结
Java线程生命周期是理解并发编程的基础,核心是掌握5种状态的定义和转换条件,以及关键方法对状态的影响。线程创建的4种方式中,前3种是基础,而线程池是生产环境的首选方案。
理解这些知识点不仅能帮助你通过面试,更能让你在实际开发中避免常见的并发问题,写出更高效、更健壮的并发代码。后续学习可以在此基础上深入了解线程同步机制、锁机制、并发容器和原子类等高级内容。
Java并发编程核心面试背诵版 + 10道高频题标准答案
第一部分:核心知识点可直接背诵版
一、Java线程生命周期(面试必背)
1. 5种核心状态(JDK Thread.State枚举定义)
- NEW(新建):线程对象已创建,未调用
start(),仅存在于JVM堆,未绑定操作系统线程 - RUNNABLE(可运行):调用
start()后进入,包含操作系统"就绪"和"运行"两个子状态 - BLOCKED(阻塞):等待
synchronized锁时进入,锁释放后自动回到RUNNABLE - WAITING(无限等待):调用
wait()/join()/LockSupport.park()进入,必须由其他线程显式唤醒 - TIMED_WAITING(超时等待):调用
wait(long)/sleep(long)/join(long)进入,超时或被唤醒后回到RUNNABLE - TERMINATED(终止):线程执行完毕或异常退出,生命周期结束,无法再次启动
2. 关键方法背诵要点
start():唯一能启动新线程的方法,将线程从NEW转为RUNNABLE,由JVM调用run()run():只是普通方法,直接调用不会启动新线程,仍在当前线程执行wait():Object类方法,必须在同步块中调用,会释放对象锁,需notify()/notifyAll()唤醒sleep():Thread类静态方法,任何地方可调用,不释放任何锁,超时自动唤醒join():让当前线程等待目标线程执行完毕,底层基于wait()实现,会释放锁interrupt():不直接终止线程,仅设置中断标志位;对等待状态线程会抛出InterruptedException并清除标志
二、Java线程创建的4种方式(面试必背)
1. 继承Thread类
- 步骤:继承Thread → 重写run() → 创建实例 → 调用start()
- 优点:实现简单,
this即当前线程 - 缺点:Java单继承限制,任务与线程耦合,不利于复用
2. 实现Runnable接口
- 步骤:实现Runnable → 重写run() → 传入Thread构造器 → 调用start()
- 优点:避免单继承,任务与线程分离,同一任务可被多个线程执行
- 缺点:无法获取返回值,不能抛出受检异常
3. 实现Callable接口+FutureTask
- 步骤:实现Callable → 重写call() → 包装为FutureTask → 传入Thread → 调用start() → get()获取结果
- 优点:支持泛型返回值,可抛出受检异常
- 缺点:实现复杂,
get()方法会阻塞当前线程
4. 使用线程池(Executor框架)
- 步骤:创建线程池 → 提交Runnable/Callable任务 → 关闭线程池
- 优点:降低资源消耗(复用线程)、提高响应速度、统一管理线程、支持定时/周期执行
- 缺点:需合理配置参数,否则可能导致性能问题
5. 选型优先级(生产环境)
线程池 > Callable+Future(需返回值) > Runnable(无返回值) > 继承Thread类
第二部分:10道高频面试题标准答案
1. 简述Java线程的5种状态及转换条件
标准答案:
Java线程在java.lang.Thread.State枚举中定义了5种核心状态,转换流程如下:
- NEW → RUNNABLE:调用线程对象的
start()方法 - RUNNABLE → BLOCKED:线程尝试获取
synchronized锁但失败 - BLOCKED → RUNNABLE:其他线程释放锁,当前线程成功获取
- RUNNABLE → WAITING:调用
wait()、join()或LockSupport.park()方法 - WAITING → RUNNABLE:其他线程调用
notify()/notifyAll()或LockSupport.unpark() - RUNNABLE → TIMED_WAITING:调用
wait(long)、sleep(long)、join(long)等带超时参数的方法 - TIMED_WAITING → RUNNABLE:超时时间到或被其他线程唤醒
- RUNNABLE → TERMINATED:线程
run()方法执行完毕或抛出未捕获的异常
2. wait()和sleep()的区别是什么?
标准答案:
两者最核心的区别是是否释放锁,具体对比如下:
| 对比项 | wait() | sleep() |
|---|---|---|
| 所属类 | Object类 | Thread类 |
| 锁行为 | 释放持有的对象锁 | 不释放任何锁 |
| 调用条件 | 必须在synchronized同步块/方法中 |
任何地方都可调用 |
| 唤醒方式 | 其他线程调用notify()/notifyAll()或超时 |
超时时间到或被interrupt() |
| 设计目的 | 用于线程间通信 | 用于线程暂停执行 |
3. start()和run()的区别是什么?
标准答案:
start():是启动线程的唯一正确方法。调用后,JVM会创建一个新的操作系统线程,并将其状态从NEW转为RUNNABLE,当该线程获得CPU时间片时,JVM会自动调用其run()方法执行任务逻辑。run():只是Thread类或Runnable接口定义的普通方法。直接调用run()不会启动新线程,任务逻辑会在当前调用线程中同步执行,失去了多线程的意义。
4. 如何优雅地终止一个线程?
标准答案:
Java没有提供强制安全终止线程的方法,推荐使用中断机制优雅终止线程,步骤如下:
- 在线程内部定期检查中断标志位(
Thread.currentThread().isInterrupted()) - 当检测到中断标志位为true时,清理资源并正常退出
run()方法 - 对于处于WAITING/TIMED_WAITING状态的线程,捕获
InterruptedException后处理中断
代码示例:
public class GracefulStopThread implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 捕获异常后重新设置中断标志位
Thread.currentThread().interrupt();
break;
}
}
// 清理资源
}
public static void main(String[] args) {
Thread thread = new Thread(new GracefulStopThread());
thread.start();
// 一段时间后中断线程
thread.interrupt();
}
}
5. 为什么不推荐使用stop()方法终止线程?
标准答案:stop()方法已被JDK废弃,因为它是不安全的,主要问题有:
- 强制释放所有锁:会立即释放线程持有的所有监视器锁,导致被锁保护的共享数据处于不一致状态
- 无法清理资源:线程会被立即终止,没有机会执行资源清理操作(如关闭文件、释放连接)
- 不可预测性:线程可能在执行任何代码时被终止,导致程序出现难以调试的bug
6. Java创建线程有哪几种方式?各有什么优缺点?
标准答案:
Java创建线程主要有4种方式:
- 继承Thread类
- 优点:实现简单,直接使用
this即可获取当前线程 - 缺点:受Java单继承限制,任务与线程耦合度高,不利于代码复用
- 优点:实现简单,直接使用
- 实现Runnable接口
- 优点:避免单继承限制,任务与线程分离,同一任务可被多个线程执行
- 缺点:无法获取线程执行结果,不能抛出受检异常
- 实现Callable接口+FutureTask
- 优点:支持泛型返回值,可以抛出受检异常
- 缺点:实现相对复杂,
get()方法会阻塞当前线程
- 使用线程池(Executor框架)
- 优点:降低资源消耗(复用线程)、提高响应速度、统一管理线程、支持定时/周期执行
- 缺点:需要合理配置线程池参数,否则可能导致性能问题
7. Callable和Runnable的区别是什么?
标准答案:
两者都是用于定义线程执行任务的接口,主要区别如下:
| 对比项 | Runnable | Callable |
|---|---|---|
| 方法签名 | void run() |
V call() throws Exception |
| 返回值 | 无 | 有泛型返回值 |
| 异常处理 | 不能抛出受检异常,只能内部捕获 | 可以抛出受检异常 |
| 使用方式 | 可直接传入Thread或线程池 | 需包装为FutureTask后传入Thread或线程池 |
| 结果获取 | 无法获取执行结果 | 通过Future.get()获取结果 |
8. 为什么推荐使用线程池而不是手动创建线程?
标准答案:
手动创建线程存在以下问题:
- 资源消耗高:频繁创建和销毁线程会消耗大量CPU和内存资源
- 响应速度慢:任务到达时需要等待线程创建完成才能执行
- 缺乏管理:无限制创建线程会导致系统过载,甚至OOM
- 功能单一:不支持定时执行、周期执行等高级功能
线程池通过线程复用解决了上述问题,同时提供了统一的线程管理机制,是生产环境并发编程的首选方案。
9. 一个线程两次调用start()方法会发生什么?
标准答案:
会抛出IllegalThreadStateException异常。
原因:每个线程只能启动一次。当第一次调用start()后,线程状态会从NEW变为RUNNABLE,JVM会将该线程标记为已启动。当再次调用start()时,JVM会检测到线程已经启动过,从而抛出异常。即使线程已经执行完毕进入TERMINATED状态,也不能再次调用start()。
10. 线程的中断机制是如何工作的?
标准答案:
Java线程中断是一种协作式的线程终止机制,不是强制终止,工作原理如下:
- 每个线程都有一个
interrupted标志位,用于标记是否被中断 - 调用
thread.interrupt()方法会将该线程的中断标志位设置为true - 对于处于RUNNABLE状态的线程,仅设置标志位,需要线程主动检查
isInterrupted()来响应中断 - 对于处于WAITING/TIMED_WAITING状态的线程,调用
interrupt()会使其抛出InterruptedException,并清除中断标志位 - 可以通过
Thread.interrupted()静态方法检查当前线程是否被中断,并清除中断标志位;通过isInterrupted()实例方法检查中断状态,不清除标志位
补充:面试加分技巧
- 回答状态转换时,主动提到"JVM的RUNNABLE状态包含操作系统的就绪和运行两个子状态",体现对底层的理解
- 回答
wait()和sleep()区别时,先说出"是否释放锁"这个核心区别,再展开其他点 - 回答线程创建方式时,最后一定要强调"生产环境优先使用线程池",并说明原因
- 回答中断机制时,主动区分
interrupted()和isInterrupted()的区别,展示细节掌握程度
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)