摘要:本文是 Java 并发编程系列的第一篇,覆盖**模块 1(基础层)**的全部内容——三种线程创建方式及其底层原理、线程六种生命周期状态与完整转换图、
Thread核心方法(sleep/wait/yield/join/interrupt/守护线程)的深度解析。每个知识点均配有可运行代码、原理拆解与面试速记,文末附总结。
目录
一、三种方式创建线程
1.1 继承 Thread 类
1 | class MyThread extends Thread { |
原理:Thread 类本身实现了 Runnable 接口。当你继承 Thread 并重写 run() 时,实际上是在覆写 Thread.run() 的默认行为(默认行为是调用构造时传入的 target.run())。
🔑 速记:继承
Thread= 直接把「任务」和「线程」绑在一起。简单但不够灵活。
1.2 实现 Runnable 接口
1 | class MyRunnable implements Runnable { |
Lambda 表达式写法(最常用、最简洁):
1 | Thread t = new Thread(() -> { |
原理:Runnable 是一个函数式接口(@FunctionalInterface),只有一个抽象方法 run()。它只定义”要做什么”,不关心”怎么调度”。Thread 类的构造方法接受一个 Runnable 对象作为任务目标,start() 时由新线程去执行这个任务的 run()。
🔑 速记:
Runnable= 纯任务,自己不能启动;必须包一层Thread或丢给线程池才能跑。
1.3 实现 Callable 接口(带返回值)
1 | import java.util.concurrent.Callable; |
关键角色拆解:
| 角色 | 职责 |
|---|---|
Callable<V> |
定义有返回值的任务(类似 Runnable 但 call() 能抛异常、能返回结果) |
FutureTask<V> |
桥梁——同时实现了 Runnable 和 Future,既能被 Thread 执行,又能通过 get() 取结果 |
Future.get() |
阻塞当前线程,等待异步任务完成并取回返回值 |
🔑 速记:
Callable单独不能跑 → 包一层FutureTask变成Runnable→ 再给Thread或线程池。get()是拿结果的入口,没算完就一直等。
1.4 三种方式的底层关系与选择指南
三者之间的关系(一张图看懂):
1 | Thread (extends Object implements Runnable) |
完整对比表:
| 维度 | 继承 Thread |
实现 Runnable |
实现 Callable |
|---|---|---|---|
| 是否有返回值 | ❌ | ❌ | ✅ FutureTask.get() |
| 能否抛出受检异常 | ❌ 只能 try-catch | ❌ 同左 | ✅ 声明在 call() 上 |
| 继承限制 | ⚠️ 占用单继承名额 | ✅ 不影响继承 | ✅ 不影响继承 |
| 如何启动 | new X().start() |
new Thread(x).start() |
new Thread(new FutureTask(x)).start() |
| 适用场景 | 简单演示 | 无返回值任务(最常用) | 需要返回值的任务 |
| 线程池兼容 | ❌ 不方便 | ✅ 直接提交 | ✅ 提交后返回 Future |
💡 面试提示:实际开发优先用
Runnable/Callable+ 线程池,几乎不用继承Thread。原因有三:
- 避免单继承限制——你的业务类很可能需要继承别的框架类;
- 任务与线程解耦——同一个
Runnable可以交给不同线程或线程池复用;- 线程池只认
Runnable/Callable——ExecutorService.submit()不接受Thread子类。
二、线程生命周期与状态(核心)
这是模块 1 最重要的一节。面试必问,不理解后面所有同步手段都是在死记。
2.1 六种状态一览
Java 线程在任意时刻只能处于以下六种状态之一(定义在 Thread.State 枚举中):
| 状态 | 英文常量 | 含义 | 类比 |
|---|---|---|---|
| 新建 | NEW |
已创建但还没 start() |
招了人但还没入职 |
| 可运行 | RUNNABLE |
就绪态(排队等 CPU)+ 运行态(正在用 CPU) | 在工位上干活或等着轮到自己 |
| 阻塞 | BLOCKED |
等待获取监视器锁(synchronized) | 在等同事释放会议室钥匙 |
| 等待 | WAITING |
无限期等别人唤醒(wait()/join()/park()) |
发了消息等对方回复,没有超时 |
| 限时等待 | TIMED_WAITING |
有时限地等(sleep()/wait(timeout)/join(timeout)) |
设了闹钟休息一会儿 |
| 终止 | TERMINATED |
run() 执行完毕或异常退出 |
离职了 |
关键认知:Java 的
RUNNABLE包含了操作系统层面的**就绪(Ready)+ 运行(Running)**两种状态,JVM 层面不做区分。所以一个线程处于RUNNABLE不代表它一定在消耗 CPU——它可能在等 I/O、等网络响应。
2.2 状态转换全景图
1 | start() |
记忆口诀:新建启动变可运行;锁不到就 BLOCKED;睡/等有时限进 TIMED;无限等进 WAITING;跑完挂掉 TERMINATED;全部回来都靠唤醒/中断/到期。
2.3 每种状态的触发条件与代码演示
① NEW → RUNNABLE
1 | Thread t = new Thread(() -> {}); // NEW |
② RUNNABLE → TIMED_WAITING(sleep)
1 | Thread t = new Thread(() -> { |
③ RUNNABLE → BLOCKED(synchronized 抢锁失败)
1 | Object lock = new Object(); |
④ RUNNABLE → WAITING(wait / join)
1 | // --- wait() 场景 --- |
⑤ → TERMINATED
1 | Thread t = new Thread(() -> {}); |
2.4 面试高频:BLOCKED vs WAITING vs TIMED_WAITING
这三个状态最容易混淆,用一句话区分:
| 状态 | 为什么等 | 谁让你恢复 | 典型场景 |
|---|---|---|---|
| BLOCKED | 被动等锁——你想进入 synchronized 块但锁被别人占着 |
锁的持有者主动退出同步块释放锁 | 多个线程竞争同一把 synchronized 锁 |
| WAITING | 主动等通知——你调用了 wait()/join()/LockSupport.park() 放弃执行权 |
必须被显式唤醒:notify()/join 结束/unpark() |
生产者-消费者模式、线程间协调 |
| TIMED_WAITING | 主动等,但有闹钟——你带了超时参数 | 时间到了自动醒,或被提前中断/唤醒 | sleep() 限流、wait(timeout) 带超时等待 |
💡 面试提示:面试官最爱问「
sleep()之后线程是什么状态?」答TIMED_WAITING,不是RUNNABLE也不是BLOCKED。再追问「synchronized没拿到锁呢?」答BLOCKED。记住:BLOCKED 是被动的(锁不给你),WAITING/TIMED_WAITING 是主动的(你自己放弃的)。
三、核心方法深度解析
3.1 currentThread() 与线程基本信息
Thread.currentThread() 是一个静态方法,返回正在执行这段代码的那个线程对象。
1 | public class Demo { |


toString() 格式解读:Thread[线程名, 优先级, 线程组名]
常用信息获取方法汇总:
| 方法 | 返回类型 | 说明 |
|---|---|---|
getName() / setName(name) |
String / void |
线程名(默认 Thread-0、Thread-1…) |
getId() |
long |
线程唯一 ID(全局递增,不可修改) |
getPriority() / setPriority(p) |
int / void |
优先级(1–10,默认 5) |
getState() |
Thread.State |
当前状态(六种枚举值之一) |
getThreadGroup() |
ThreadGroup |
所属线程组 |
isAlive() |
boolean |
是否存活(start() 后且未终止) |
isDaemon() |
boolean |
是否守护线程 |
优先级细节:
1 | Thread t = new Thread(() -> {}); |
⚠️ 重要认知:优先级越高只是获得 CPU 调度的概率越大,绝不保证先执行或不被抢占。且不同操作系统对 Java 优先级(1–10)的映射不同(Linux 只有 1:1 和 1:N 两种线程模型,Windows 映射更复杂)。生产环境永远不要依赖优先级来保证执行顺序。

🔑 速记:
currentThread()= “我是谁”;优先级 = “老板偏心但不保准”。
3.2 run() 与 start() 的本质区别
这是初学者第一坑,也是面试送分题。
1 | public class Demo { |

start() 内部发生了什么?
- 将线程状态从
NEW→RUNNABLE - 加入线程组,通知 JVM 该线程已就绪
- 由操作系统的线程调度器分配 CPU 时间片
- 当该线程获得 CPU 后,JVM 自动调用其
run()方法 run()执行完毕后线程进入TERMINATED状态
💡 面试提示:
start()只能调用一次,第二次会抛IllegalThreadStateException(因为状态已经不是NEW了)。- 直接调
run()不会报错,但只是普通方法调用——不会创建新线程、不会有并发效果。start()是synchronized方法(Thread源码里加了锁),保证线程状态变更的原子性。
3.3 sleep() —— 抱着锁睡
1 | // 基本用法 |
sleep() 的三个关键特性:
| 特性 | 说明 |
|---|---|
| 不释放锁 | 如果当前线程持有 synchronized 锁,sleep 期间锁仍然被占用 |
| 静态方法 | Thread.sleep() 作用在当前线程上,不是调用它的那个线程对象 |
| 状态变化 | RUNNABLE → TIMED_WAITING → 时间到 / 被中断 → 回到 RUNNABLE |
为什么 sleep() 要捕获 InterruptedException?
因为 sleep() 是一种可中断的阻塞。当另一个线程对你调用了 interrupt(),JVM 会把你从睡眠中唤醒(抛异常),而不是让你继续傻睡到底。这是 Java 协作式中断机制的一部分——给了线程一个”优雅退出的机会”。
🔑 记忆钩子:
sleep抱锁睡——抱着 synchronized 锁睡觉,不放手;醒来后继续持有。
3.4 wait() —— 放开锁等
1 | Object lock = new Object(); |
wait() 的五个铁律:
| 规则 | 说明 |
|---|---|
必须在 synchronized 块内调用 |
否则抛 IllegalMonitorStateException |
| 调用后立即释放锁 | 这是和 sleep() 最大的区别 |
| 释放的是当前持有的那把锁 | 不是所有锁,是你当前 synchronized(lock) 里的那个 lock 对象 |
| 被唤醒后重新竞争锁 | notify() 只是把它从等待队列移到锁竞争队列,还得抢到锁才能真正执行 |
属于 Object 的方法 |
不是 Thread 的方法——任何对象都能 wait()/notify() |
🔑 记忆钩子:
wait放锁等——放开 synchronized 锁等别人叫;醒了还要重新抢锁。
3.5 sleep() vs wait() 完整对比
| 对比维度 | sleep() |
wait() |
|---|---|---|
| 所属类 | Thread(静态方法) |
Object(实例方法) |
| 锁行为 | 不释放锁 | 释放锁 |
| 使用位置 | 任何地方 | 必须在 synchronized 块内 |
| 唤醒方式 | 时间到 / interrupt() |
notify()/notifyAll() / interrupt() |
| 用途 | 控制自身执行节奏 | 线程间通信/协作 |
| 作用对象 | 当前线程 | 调用它的那个锁对象上的等待者 |
| 典型场景 | 简单限流、模拟耗时 | 生产者-消费者、条件等待 |
💡 面试提示:面试必问。记住核心区别只有两个字:锁。
sleep不放、wait放。其他区别都是从这个根区别衍生出来的(比如wait必须在synchronized里用——因为它要释放锁,没锁怎么释放?)。
3.6 如何停止线程——interrupt() 协作式中断
❌ 已废弃的方式:stop()、suspend()、resume()——它们强制停止/挂起线程,可能导致:
- 对象处于不一致的中间态(锁被突然释放)
finally块来不及执行清理资源- 无法预测程序状态
✅ 正确方式:interrupt() 协作式中断
interrupt() 不是强制杀死线程,而是设置一个中断标志位(boolean flag),线程自己决定何时检查这个标志并安全退出。
方式一:通过 isInterrupted() 自行检查
1 | Thread t = new Thread(() -> { |
方式二:通过 volatile 标志位
1 | public class VolatileStop { |
两种方式的选择:
| 方式 | 优点 | 缺点 |
|---|---|---|
interrupt() |
JDK 内置,能唤醒 sleep()/wait() 中的线程 |
需要在代码里主动检查 |
volatile flag |
简单直观,逻辑清晰 | 无法唤醒阻塞中的 sleep()/wait()——线程卡在 sleep 里根本读不到 flag |
最佳实践:两者结合——用
interrupt()作为主要手段,volatile 作为补充。

💡 面试提示:为何不用
stop()?→ 强制释放锁导致数据不一致。正确姿势是interrupt()+ 线程自行检查退出。追问「sleep()中被 interrupt 会怎样?」→ 抛InterruptedException并清除中断标志,应在 catch 里做清理或重新设置标志。
3.7 yield() —— 礼让 CPU
1 | Thread t = new Thread(() -> { |
yield() 的本质:
- 让当前线程从运行态回到就绪态(注意:不是阻塞态,是不拿 CPU 但仍可被调度)
- 不释放任何锁
- 只是”建议”——操作系统完全可以忽略这次礼让(大多数情况下确实会被忽略)
- 状态变化:
RUNNABLE→RUNNABLE(还是同一种状态,只是放弃了本次时间片)
实际价值很低。现代 JVM 调度器已经足够智能,手动
yield()几乎不会带来性能提升。主要用于调试或特殊框架代码。
🔑 记忆钩子:
yield礼让——“我不急,你们先”,但别人不一定领情。
3.8 join() —— 插队等待
1 | Thread t = new Thread(() -> { |
join() 内部其实就是 wait()(源码层面用 wait(0) 实现):
1 | // Thread.join() 的简化版逻辑 |
所以调用 t.join() 的主线程会进入 WAITING 状态,直到 t 线程终止后 JVM 会自动调用 this.notifyAll() 唤醒所有等待者。
带超时的版本:
1 | t.join(1000); // 最多等 1 秒,超时后不管 t 有没有跑完都继续 |
🔑 记忆钩子:
join插队——“你做完我再做”。内部就是wait(0),所以主线程会进入 WAITING/TIMED_WAITING。
3.9 守护线程(Daemon)
1 | Thread daemon = new Thread(() -> { |
守护线程的关键规则:
| 规则 | 说明 |
|---|---|
必须在 start() 前设置 |
运行中设 setDaemon() 会抛 IllegalThreadStateException |
| 不阻止 JVM 退出 | 当所有非守护线程结束时,JVM 直接退出,守护线程被”斩立决” |
finally 不保证执行 |
JVM 退出时守护线程可能正执行到一半,finally 来不及跑 |
| 子线程继承父线程属性 | 主线程创建的子线程默认是非守护线程;守护线程创建的子线程默认也是守护线程 |
典型应用场景:
- GC 线程(
Finalizer) - 日志异步刷盘线程
- 心跳检测 / 监控指标采集线程
- 各种后台清理任务
💡 面试提示:守护线程最大的坑是
finally不一定执行。所以不要把”必须完成的资源清理”放在守护线程里。判断是否要用守护线程的标准:“这个线程被突然杀掉,会不会导致数据丢失或状态不一致?” 如果会,就用非守护线程。
四、总结
核心速记
创建线程三方式:
Thread继承(简单但占继承名额)、Runnable实现(最常用)、Callable+FutureTask(有返回值)。实际开发优先Runnable/Callable+ 线程池。六种状态:NEW → RUNNABLE → (BLOCKED 抢锁 / WAITING 无限等 / TIMED_WAITING 限时等)→ TERMINATED。BLOCKED 是被动的(锁不给),WAITING 是主动的(自己放弃的)。
方法记忆链:
sleep抱锁睡、wait放锁等、yield礼让、join插队、interrupt协作停、setDaemon守护不阻退出。
start()≠run():前者创建新线程并自动调用后者;直接调run()只是普通方法调用,无并发效果。
最佳实践 Checklist
- 创建线程优先
Runnable/Callable,不用继承Thread - 启动线程用
start(),绝不直接调run() - 停止线程用
interrupt()协作式中断,不用废弃的stop() -
sleep()用于简单限流,wait()/notify()用于线程间协作 - 守护线程只做”可中断”的后台任务,不放重要资源清理
- 不要依赖线程优先级保证执行顺序
-
join()用于等待结果,但要设超时避免永久阻塞
高频面试题速答
| 问题 | 一句话答案 |
|---|---|
start() 和 run() 区别? |
start() 创建新线程并自动调用 run();直接调 run() 是普通方法调用,不开新线程 |
sleep() 和 wait() 区别? |
sleep 不释放锁(Thread 静态方法);wait 释放锁(Object 方法,需在 synchronized 内) |
| 怎么正确停止线程? | interrupt() 设置标志位 + 线程内检查 isInterrupted() 或捕获 InterruptedException |
| 线程有哪些状态? | 六种:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED |
|yield()做什么? | 礼让 CPU,回到就绪态重新竞争(只是建议,不保证生效) |
|join()原理? | 内部用wait(0)实现,调用线程进入 WAITING 直到目标线程终止 |
| 守护线程特点? | 不阻止 JVM 退出,finally不保证执行,必须在start()前设置 |
|BLOCKED和WAITING区别? | BLOCKED 是被动等锁(synchronized),WAITING 是主动放弃(wait/join/park) |




