多线程
- 基础概念
-
- [线程与进程 ⭐](#线程与进程 ⭐)
- 并发与并行⭐
- 线程的基本使用(Thread常用方法)
-
- 常见构造方法
- 创建线程的4种方式
-
- 1) 继承 Thread 类 继承 Thread 类)
- 2) 实现 `Runnable` 接口 实现
Runnable接口) - 3) 使用匿名内部类(基于 Thread) 使用匿名内部类(基于 Thread))
- 4) 使用匿名内部类(基于 Runnable) 使用匿名内部类(基于 Runnable))
- 5) 基于 Lambda 表达式 基于 Lambda 表达式)
- 将start()换成run()有什么不同?⭐
- [为什么 start()不能调用两次?⭐](#为什么 start()不能调用两次?⭐)
- Thread类与Runnable接口的关系⭐
- 常用属性、获取与设置方法
-
- [守护线程/后台线程:Daemon ⭐](#守护线程/后台线程:Daemon ⭐)
-
- [守护线程中的 finally 不一定执行](#守护线程中的 finally 不一定执行)
- 新线程会继承父线程的守护状态
- 守护线程不要持有需要关闭的资源
- 获取当前线程引用:currentThread()
- 休眠当前线程:sleep()
- [中断一个线程:interrupt ⭐](#中断一个线程:interrupt ⭐)
-
- [中断与阻塞方法的交互(总结) ⭐](#中断与阻塞方法的交互(总结) ⭐)
- 中断标志丢失问题⭐
- [等待一个线程: join()⭐](#等待一个线程: join()⭐)
- 线程的终止
- 线程礼让:yield()
- 线程生命周期:6种状态详解
基础概念
线程与进程 ⭐
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源拥有 | 独立的内存空间、文件句柄等 | 共享进程的资源 |
| 创建开销 | 大(fork成本高) | 小(几KB栈空间) |
| 切换成本 | 高(涉及页表切换) | 低(保留CPU上下文即可) |
| 通信方式 | IPC(管道、消息队列、共享内存) | 直接访问共享变量 |
| 崩溃影响 | 不影响其他进程 | 可能导致整个进程崩溃 |
进程是资源分配的最小单位,线程是CPU调度的最小单位。
补充资料
并发与并行⭐
| 对比维度 | 并发 (Concurrency) | 并行 (Parallelism) |
|---|---|---|
| 核心定义 | 多个任务交替执行,宏观同时 | 多个任务真正同时执行 |
| 硬件要求 | 单核或多核均可 | 必须多核CPU |
| 执行方式 | 时间片轮转,微观串行 | 每个核心独立执行一个任务 |
| 目的 | 提高程序的响应性和资源利用率 | 提高计算速度和吞吐量 |
线程的基本使用(Thread常用方法)
常见构造方法
| 方法 | 说明 |
|---|---|
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
| Thread(String name) | 创建线程对象,并命名 |
| Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
| 【了解】Thread(ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组即为线程目标 |
java
// 构造方法示例
Thread t1 = new Thread(); // 无参构造
Thread t2 = new Thread(() -> System.out.println("run")); // Runnable参数
Thread t3 = new Thread("myThread"); // 指定名称
Thread t4 = new Thread(() -> System.out.println("run"), "myThread"); // 名称+Runnable
t2和t4看不懂先看 《创建线程的4种方式》这部分
创建线程的4种方式
1) 继承 Thread 类
- 通过创建一个继承自
Thread的子类并重写run()方法定义线程执行的任务。 - 启动线程时,调用
start()方法,start()方法会自动调用run()方法。
java
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承 Thread 创建线程");
}
}
// 使用方式
MyThread t = new MyThread();
t.start();
2) 实现 Runnable 接口
- 通过重写
Runnable接口中的run()方法,定义线程执行的任务。 - 将
Runnable实例传递给Thread类的构造函数,然后调用start()方法启动线程。
java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现 Runnable 创建线程");
}
}
// 使用方式
Thread t = new Thread(new MyRunnable());
t.start();
3) 使用匿名内部类(基于 Thread)
不需要显式定义子类,直接在创建对象时重写方法。
java
Thread t = new Thread() {
@Override
public void run() {
System.out.println("匿名内部类 (Thread)");
}
};
t.start();
4) 使用匿名内部类(基于 Runnable)
将匿名实现的 Runnable 对象作为参数传递给 Thread。
java
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类 (Runnable)");
}
});
t.start();
5) 基于 Lambda 表达式
由于 Runnable 是一个函数式接口,在 Java 8 及更高版本中,这是最简洁、最常用的写法。
java
// 一行代码搞定
new Thread(() -> System.out.println("Lambda 表达式创建线程")).start();
将start()换成run()有什么不同?⭐
start() 是开启新线程;run() 只是普通的方法调用。
-
A. 调用
start()的情况(多线程并发) ,当调用t.start()时:- 申请资源:Java 向操作系统申请创建一个新的线程。
- 状态切换:线程进入"可运行(Runnable)"状态。
- 并发执行 :一旦 CPU 分配了时间片,新线程就开始执行自己的
run()。此时,原线程并不会停下来等它 ,两个线程在并行/并发跑。

-
B. 调用
run()的情况(单线程同步) ,当调用t.run()时:- 直接调用:JVM 不会创建任何新线程。
- 同步阻塞 :代码会直接跳进
run()方法里去执行。 - 单线流向 :必须等
run()里的循环或耗时操作彻底结束,程序计数器才会回到主线程刚才的位置继续往下走。

| 特性 | t.start() |
t.run() |
|---|---|---|
| 是否启动新线程 | 是。会请求 JVM 开启一个独立的新线程。 | 否。就在当前调用它的线程中执行。 |
| 执行逻辑 | 让新线程进入就绪状态,等待 CPU 调度后自动执行 run()。 |
像调用普通函数一样,同步执行 run() 里的代码。 |
| 是否阻塞主线程 | 不阻塞。主线程会继续往下走。 | 阻塞 。必须等 run() 执行完,后面的代码才能走。 |
| 调用次数 | 每个线程对象只能调用 1 次,否则报异常。 | 可以调用无数次,因为它就是个普通方法。 |
为什么 start()不能调用两次?⭐
Thread类内部有一个threadStatus变量,初始值为 0。- 只有当状态为 0 时,
start()才会执行。执行后状态会改变。 - 如果第二次调用
start(),它会抛出IllegalThreadStateException异常。
Thread类与Runnable接口的关系⭐
Thread类源码层面的关系
java
// Thread类的定义(简化版)
public class Thread implements Runnable {
private Runnable target; // 持有Runnable任务对象
// 构造函数:传入Runnable任务
public Thread(Runnable target) {
this.target = target;
}
// 核心run方法:Thread重写了Runnable的run()
@Override
public void run() {
if (target != null) {
target.run(); // 委托给传入的Runnable任务
}
}
// 启动线程(native方法,由JVM调度)
public synchronized void start0() {
// 底层创建操作系统线程,并调用run()
}
}
Thread implements Runnable:Thread本身就是Runnable的实现类Thread.run()默认委托给target.run()(构造函数传入的Runnable)- 如果继承Thread并重写run(),则覆盖上述委托逻辑
关系图示
┌─────────────────┐
│ <<interface>> │
│ Runnable │
├─────────────────┤
│ +run(): void │
└────────▲────────┘
│ implements
┌────────┴────────┐
│ Thread │
├─────────────────┤
│ -target: Runnable│
├─────────────────┤
│ +Thread(Runnable)│
│ +start(): void │
│ +run(): void │
└─────────────────┘
常用属性、获取与设置方法
| 属性 | 获取方法 | 设置方法 | 备注与重要规则 |
|---|---|---|---|
| ID | getId() |
❌ 无 | 线程的唯一标识,由 JVM 自动分配且不可修改。 |
| 名称 | getName() |
setName(String name) |
如果不手动设置,JVM 默认命名为 Thread-0, Thread-1 等。 |
| 状态 | getState() |
❌ 无 | 返回枚举类型 Thread.State,只能由 JVM 运行状态决定,不可人为设置。 |
| 优先级 | getPriority() |
setPriority(int newPriority) |
范围 1~10,默认 5。main函数默认是5 |
| 是否后台线程 | isDaemon() |
setDaemon(boolean on) |
守护线程。必须在 start() 之前调用设置,否则会抛出 IllegalThreadStateException 异常。 |
| 是否存活 | isAlive() |
❌ 无 | 测试线程是否处于活动状态(已启动且尚未终止)。这是线程的生命周期状态,不可人为设置。 |
| 是否被中断 | isInterrupted() |
interrupt() |
注意:设置方法不叫 setInterrupted(),叫 interrupt()。它的作用是打招呼/发信号,而不是强行杀死线程。 |
java
//简单使用
Thread thread = new Thread(() -> {
System.out.println("线程执行中...");
}, "worker");
thread.start();
System.out.println("ID: " + thread.getId()); // 输出: ID: 14
System.out.println("名称: " + thread.getName()); // 输出: 名称: worker
System.out.println("状态: " + thread.getState()); // 输出: 状态: RUNNABLE
System.out.println("优先级: " + thread.getPriority()); // 输出: 优先级: 5(默认)
System.out.println("是否后台: " + thread.isDaemon()); // 输出: 是否后台: false
System.out.println("是否存活: " + thread.isAlive()); // 输出: 是否存活: true
thread.getState()状态在《 线程生命周期:6种状态详解》中详细说明
守护线程/后台线程:Daemon ⭐
守护线程是一种特殊的线程,它的作用是为其他线程(用户线程)提供后台服务 。当程序中所有用户线程都执行完毕 时,JVM 会自动退出,并无情地终止所有还在运行的守护线程。
- 用户线程 :像主角,JVM 会等它演完才结束(例如
main线程) - 守护线程:像配角,主角都散场了,配角也就没必要存在了,所以可以处理垃圾回收(GC)、日志监控、心跳检测
java
// 判断是否为守护线程
Thread thread = new Thread();
System.out.println(thread.isDaemon()); // false(默认为用户线程)
工作线程设置守护线程,使用 setDaemon(true) 方法,必须在 start() 之前调用:
java
public class DaemonExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
int count = 0;
while (true) { // 无限循环
System.out.println("守护线程工作中... " + ++count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 必须先设置为守护线程,再启动
daemonThread.setDaemon(true);
daemonThread.start();
// 用户线程:主线程只运行3秒
System.out.println("主线程开始工作...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束!");
// 此时所有用户线程(主线程)已结束,JVM 退出
// 守护线程会被强制终止,不会无限打印下去
}
}
输出结果:
text
主线程开始工作...
守护线程工作中... 1
守护线程工作中... 2
守护线程工作中... 3
守护线程工作中... 4
守护线程工作中... 5
守护线程工作中... 6
主线程结束!
守护线程中的 finally 不一定执行
由于守护线程被强制终止 ,finally 块可能不会执行:
java
public class DaemonFinallyIssue {
public static void main(String[] args) {
Thread daemon = new Thread(() -> {
try {
while (true) {
System.out.println("守护线程运行中");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// ⚠️ 当 JVM 退出时,这段代码不一定执行!
System.out.println("守护线程 finally 执行");
}
});
daemon.setDaemon(true);
daemon.start();
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("主线程结束");
// JVM 退出,守护线程被强制终止,finally 可能不执行
}
}
输出结果:
text
守护线程运行中
守护线程运行中
主线程结束
守护线程运行中
// 没有 "守护线程 finally 执行"
新线程会继承父线程的守护状态
java
Thread parent = new Thread(() -> {});
parent.setDaemon(true);
// 从 parent 线程中创建的新线程,默认也是守护线程
守护线程不要持有需要关闭的资源
java
// ❌ 不好的做法:守护线程持有文件/数据库连接
Thread badDaemon = new Thread(() -> {
Connection conn = DriverManager.getConnection(...);
while (true) {
conn.executeQuery(...); // JVM 退出时连接不会正常关闭
}
});
badDaemon.setDaemon(true);
获取当前线程引用:currentThread()
Thread.currentThread() 是一个静态方法,返回当前正在执行的线程对象的引用。
常见用途:
- 检查当前线程是否被中断:
Thread.currentThread().isInterrupted() - 在静态方法中获取线程信息
- 调试和日志记录
java
public class CurrentThreadDemo {
public static void main(String[] args) {
// main 线程
Thread mainThread = Thread.currentThread();
System.out.println("main线程:" + mainThread.getName());
// 创建新线程
Thread thread1 = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println("线程1执行中:" + current.getName());
System.out.println("线程1 ID:" + current.getId());
}, "工作线程-A");
Thread thread2 = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println("线程2执行中:" + current.getName());
}, "工作线程-B");
thread1.start();
thread2.start();
}
}
输出结果:
text
main线程:main
线程1执行中:工作线程-A
线程2执行中:工作线程-B
线程1 ID:12
不能获取已结束线程
java
Thread thread = new Thread(() -> {
System.out.println("运行中");
});
thread.start();
thread.join(); // 等待线程结束
// 线程结束后,仍然可以调用 thread 对象的方法
System.out.println(thread.getName()); // ✅ 可以
// 但 currentThread() 返回的是当前执行线程(main),不是那个已结束的线程
休眠当前线程:sleep()
Thread.sleep() 方法让当前线程暂停执行指定的时间,进入 TIMED_WAITING 状态。
| 方法 | 说明 |
|---|---|
static void sleep(long millis) |
休眠指定的毫秒数 |
static void sleep(long millis, int nanos) |
休眠毫秒+纳秒 |
java
// 让当前线程休眠 1000 毫秒(1秒)
Thread.sleep(1000);
// 毫秒 + 纳秒(更精细的控制,但大多数系统精度有限)
Thread.sleep(1000, 500000); // 1秒 + 0.5毫秒
响应中断⭐
休眠中的线程可以通过 interrupt() 提前唤醒:
java
Thread t = new Thread(() -> {
try {
Thread.sleep(10000); // 计划睡10秒
} catch (InterruptedException e) {
System.out.println("被提前唤醒了");
return;
}
});
t.start();
// 2秒后中断它
Thread.sleep(2000);
t.interrupt(); // sleep() 会立即抛出 InterruptedException

必须处理异常⭐
sleep() 会抛出 InterruptedException,必须捕获或声明抛出:
java
// 方式一:try-catch
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 处理中断,通常恢复中断状态
Thread.currentThread().interrupt();
}
// 方式二:方法签名声明抛出
public void myMethod() throws InterruptedException {
Thread.sleep(1000);
}
为什么它必须被处理?
InterruptedException 的存在是为了支持取消机制。当一个线程正在睡眠(阻塞)时,它无法主动检查自己的中断标志位。
-
唤醒机制:如果另一个线程调用了该线程的
interrupt()方法,JVM 会立即将该线程从睡眠状态中唤醒。 -
清理信号:唤醒的同时,它会抛出这个异常,以此告知开发者:"有人希望你停止当前的操作,请处理好现场。"
不释放锁
sleep() 不会释放线程持有的任何锁(synchronized锁):
java
public synchronized void test() {
// 持有锁时休眠
Thread.sleep(5000); // 其他线程无法进入这个方法
// 锁会在方法结束时释放,而非休眠时
}
不保证精确睡眠时间(sleep底层调度机制)⭐
当代码调用 Thread.sleep(1000) 时,线程的生命周期会经历以下三个关键阶段:
-
放弃 CPU(进入阻塞态)
线程主动调用
sleep,会触发内核态切换。操作系统会将该线程从 运行队列(Running Queue) 移除,放入 等待队列(Waiting Queue)。此时,线程彻底让出 CPU 资源,不消耗任何计算性能。 -
信号触发(进入就绪态)
当 1000ms 时间到达,操作系统内核的定时器发出中断信号,内核将线程的状态从阻塞修改为就绪(Runnable),并将其重新移回运行队列的末尾。
-
重新调度(等待 CPU)
线程现在只是有资格运行了。它必须等待操作系统调度器(Scheduler)根据优先级、时间片等算法重新选中它。
基于上述机制,实际的停顿时间公式可以近似看作:
实际停顿时间 = 指定的 sleep 时间 + 系统定时器精度误差 + 调度延迟 \text{实际停顿时间} = \text{指定的 sleep 时间} + \text{系统定时器精度误差} + \text{调度延迟} 实际停顿时间=指定的 sleep 时间+系统定时器精度误差+调度延迟
sleep(0)的含义
Thread.sleep(0);让当前线程立即放弃CPU时间片,将线程状态从"运行"转为"就绪"并等待操作系统重新调度
中断一个线程:interrupt ⭐
线程中断是 Java 提供的一种协作机制 ,用于通知线程"你应该停止了"。它不是强制终止线程,而是设置一个中断标志,由线程自己决定如何响应。
| 方法 | 说明 |
|---|---|
void interrupt() |
设置线程的中断标志为 true |
boolean isInterrupted() |
检查当前线程的中断标志,不清除标志 |
static boolean interrupted() |
检查当前线程的中断标志,清除标志 |
┌─────────────────────────────────────────────────┐
│ 线程对象 │
│ ┌─────────────────────────────────────────┐ │
│ │ 中断标志 (interrupt flag) │ │
│ │ true / false │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
↑ ↓
interrupt() 检查方法
设置标志 isInterrupted()
interrupted()
java
public class InterruptExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("初始中断标志: " + Thread.currentThread().isInterrupted()); // false
while (!Thread.currentThread().isInterrupted()) {
System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted());
System.out.println("工作中...");
try {
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
// sleep/wait/join 被中断时会抛出异常,并清除中断标志
System.out.println("收到中断信号,准备退出");
System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted());
Thread.currentThread().interrupt(); // 重新设置中断标志
System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted());
break;
}
}
System.out.println("线程结束");
});
t.start();
Thread.sleep(3000);
t.interrupt(); // 发送中断信号
}
}
text
初始中断标志: false
当前中断标志: false
工作中...
当前中断标志: false
工作中...
当前中断标志: false
工作中...
收到中断信号,准备退出
当前中断标志: false
当前中断标志: true
线程结束

为什么要重新设置中断标志?
因为 JVM 在抛出
InterruptedException的那一刻,会把线程的中断状态重置为false。如果没有 break,且没有重新设置中断位,下一次循环检查while (!Thread.currentThread().isInterrupted())依然会通过,导致线程无法停止。
++《中断标志丢失问题⭐》也写了++
code相当于
java
public class FlagExample {
private static volatile boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (running) {
System.out.println("工作中...");
}
System.out.println("线程结束");
});
worker.start();
Thread.sleep(3000);
running = false; // 停止线程
}
}
中断与阻塞方法的交互(总结) ⭐
当线程处于某些阻塞状态 时,调用 interrupt() 会产生特殊行为:
| 方法类别 | 具体方法 | 中断响应 |
|---|---|---|
| 线程休眠 | sleep() |
抛出 InterruptedException,清除中断标志 |
| 等待机制 | wait() |
抛出 InterruptedException,清除中断标志 |
| 线程加入 | join() |
抛出 InterruptedException,清除中断标志 |
| 阻塞队列 | take(), put() |
抛出 InterruptedException |
java
public class InterruptAndSleep {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
System.out.println("线程开始睡眠...");
Thread.sleep(10000); // 睡眠10秒
System.out.println("睡眠结束");
} catch (InterruptedException e) {
// sleep 被中断,中断标志已经被清除
System.out.println("睡眠被中断");
System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted()); // false
}
});
t.start();
Thread.sleep(1000); // 等待1秒
t.interrupt(); // 中断线程
}
}
输出结果:
text
线程开始睡眠...
睡眠被中断
当前中断标志: false
中断标志丢失问题⭐
由于 sleep()/wait()/join() 会清除中断标志,通常需要重新设置:
❌ 错误:吞掉中断异常
java
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 什么都不做 ------ 中断信息丢失!
}
// 后续代码无法知道线程被中断过
✅ 正确:处理中断
java
// 方式1:重新设置标志
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复标志
}
// 方式2:直接退出
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return; // 直接返回
}
// 方式3:向上抛出
public void myMethod() throws InterruptedException {
Thread.sleep(1000);
}
等待一个线程: join()⭐
join() 是 java.lang.Thread 类的实例方法,用于等待某个线程执行完毕。调用 join()的线程会被阻塞,直到目标线程终止。
| 方法 | 说明 |
|---|---|
void join() |
无限期等待目标线程结束 |
void join(long millis) |
最多等待指定的毫秒数 |
void join(long millis, int nanos) |
最多等待毫秒+纳秒 |
java
public class JoinBasicDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始:" + Thread.currentThread().getName());
Thread worker = new Thread(() -> {
System.out.println("工作线程开始工作");
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("工作线程完成");
});
worker.start();
worker.join(); // 主线程等待worker线程完成
System.out.println("主线程结束(worker已执行完毕)");
}
}

线程的终止
Java 中安全地终止线程是一个重要话题。不同于 Thread.stop()已被废弃(不安全),Java 提供了协作式的线程终止机制。
| 方式 | 安全性 | 推荐度 | 说明 |
|---|---|---|---|
Thread.stop() |
❌ 不安全 | 不推荐 | 已废弃,可能导致数据不一致 |
| 标志位 + 检查 | ✅ 安全 | 推荐 | 最常用的协作式终止 |
interrupt() |
✅ 安全 | 推荐 | 标准的中断机制 |
volatile 标志 |
✅ 安全 | 推荐 | 适合简单场景 |
Future.cancel() |
✅ 安全 | 推荐 | 配合线程池使用 |
shutdown()/shutdownNow() |
✅ 安全 | 推荐 | 线程池终止 |
为什么不推荐 stop()?
- 立即释放所有锁,可能导致对象状态不一致
- 可能使线程在任意位置停止,造成数据损坏
线程礼让:yield()
yield() 是 java.lang.Thread 类的静态方法,用于提示调度器当前线程愿意让出 CPU 资源,让其他具有相同优先级的线程有机会执行。yield() 只是一个提示,不保证调度器一定会采纳
| 方法名 | 功能 | 说明 |
|---|---|---|
| yield() | 线程的礼让 | 礼让时间不确定,不一定礼让成功 |
java
public class YieldBasicDemo {
public static void main(String[] args) {
Thread highPriority = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("高优先级线程 - " + i);
Thread.yield(); // 让出CPU
}
}, "HighPriority");
highPriority.setPriority(Thread.MAX_PRIORITY);
Thread lowPriority = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("低优先级线程 - " + i);
Thread.yield();// 让出CPU
}
}, "LowPriority");
lowPriority.setPriority(Thread.MIN_PRIORITY);
highPriority.start();
lowPriority.start();
}
}
text
高优先级线程 - 0
低优先级线程 - 0
低优先级线程 - 1
低优先级线程 - 2
低优先级线程 - 3
低优先级线程 - 4
高优先级线程 - 1
高优先级线程 - 2
高优先级线程 - 3
高优先级线程 - 4
为什么lowPriority 中的Thread.yield()没有起到作用?
-
操作系统调度器通常遵循 "一旦上台,尽量多干点活" 的原则。上下文切换开销:把线程 A 的寄存器状态存起来,换线程 B 上来,这个操作是非常昂贵的。
-
高优先级线程执行了
yield(),操作系统这次真的把 CPU 给了低优先级线程。低优先级线程开始运行,虽然它也写了
yield(),但此时高优先级线程正处于就绪队列中等待。 -
调度器决策:由于低优先级线程的任务极其简单(只是打印一行字),它执行速度极快。在调度器还没来得及启动下一次"上下文切换"去换回高优先级线程时,低优先级线程已经利用这一个微小的时间片,一口气跑完了自己的循环。
所以说,线程优先级和 yield() 都是不可靠的(Unreliable)。
线程生命周期:6种状态详解
状态转换图
Java 线程在生命周期中有 6 种状态 ,定义在 java.lang.Thread.State 枚举中:
java
public enum State {
NEW, // 新建
RUNNABLE, // 可运行
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 定时等待
TERMINATED // 终止
}
状态转换图如下:

详细状态说明
1. NEW(新建状态)
线程对象已创建,但尚未启动
java
public class NewStateDemo {
public static void main(String[] args) {
// 线程处于 NEW 状态
Thread thread = new Thread(() -> {
System.out.println("线程执行中");
});
System.out.println("线程状态: " + thread.getState()); // NEW
System.out.println("线程是否存活: " + thread.isAlive()); // false
// 此时线程还没有开始执行
// 只能调用 start() 方法,不能调用其他方法如 run()
}
}
特点:
- 只创建了线程对象,没有分配CPU资源
- 只能调用
start()方法 - 调用
run()不会改变状态(只是普通方法调用)
2. RUNNABLE(可运行状态)
线程正在JVM中执行,但可能在等待CPU时间片
java
public class RunnableStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// 线程启动后会进入 RUNNABLE 状态
System.out.println("线程状态: " + Thread.currentThread().getState());
// 执行一些计算任务
long sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
});
thread.start();
// 可能打印 RUNNABLE(如果线程正在执行)
System.out.println("主线程看到的状态: " + thread.getState());
// RUNNABLE 包含两个子状态:
// - Ready: 就绪,等待CPU调度
// - Running: 正在执行
}
}
特点:
- 线程已启动,正在运行或准备运行
- 在Java层面,RUNNABLE 包含了操作系统的就绪和运行状态
- 这是线程最正常的工作状态
3. BLOCKED(阻塞状态)
线程在等待获取锁(synchronized)
java
public class BlockedStateDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 线程1:先获得锁
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1 获得锁");
try {
Thread.sleep(3000); // 持有锁3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1 释放锁");
}
}, "Thread-1");
// 线程2:等待锁
Thread t2 = new Thread(() -> {
System.out.println("线程2 尝试获得锁");
synchronized (lock) {
System.out.println("线程2 获得锁");
}
}, "Thread-2");
t1.start();
Thread.sleep(100); // 确保t1先获得锁
t2.start();
Thread.sleep(100); // 让t2进入BLOCKED状态
System.out.println("线程2状态: " + t2.getState()); // BLOCKED
t1.join();
t2.join();
}
}
特点:
- 等待 synchronized 锁
- 不会响应中断(除非在等待锁时被中断)
- 进入条件:尝试进入同步代码块/方法但锁被其他线程持有
4. WAITING(等待状态)
无限期等待另一个线程执行特定操作
java
public class WaitingStateDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waiter = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("等待线程: 准备等待");
lock.wait(); // 进入 WAITING 状态
System.out.println("等待线程: 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Waiter");
Thread notifier = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(2000);
System.out.println("通知线程: 准备唤醒");
lock.notify(); // 唤醒等待线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Notifier");
waiter.start();
Thread.sleep(100); // 确保waiter进入等待状态
System.out.println("等待线程状态: " + waiter.getState()); // WAITING
notifier.start();
waiter.join();
notifier.join();
}
}
进入 WAITING 状态的方式:
java
public class WaitingWaysDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 方式1: Object.wait()
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
});
// 方式2: Thread.join()(无超时)
Thread t2 = new Thread(() -> {
Thread t = new Thread(() -> {
try { Thread.sleep(5000); } catch (Exception e) {}
});
t.start();
try {
t.join(); // 等待t结束
} catch (InterruptedException e) {}
});
// 方式3: LockSupport.park()
Thread t3 = new Thread(() -> {
java.util.concurrent.locks.LockSupport.park();
});
t1.start();
t2.start();
t3.start();
Thread.sleep(100);
System.out.println("wait() 导致的状态: " + t1.getState()); // WAITING
System.out.println("join() 导致的状态: " + t2.getState()); // WAITING
System.out.println("park() 导致的状态: " + t3.getState()); // WAITING
}
}
5. TIMED_WAITING(定时等待状态)
在指定时间内等待另一个线程执行操作
java
public class TimedWaitingStateDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 方式1: Thread.sleep()
Thread t1 = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
}, "SleepThread");
// 方式2: Object.wait(timeout)
Thread t2 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(3000);
} catch (InterruptedException e) {}
}
}, "WaitTimeoutThread");
// 方式3: Thread.join(timeout)
Thread t3 = new Thread(() -> {
Thread inner = new Thread(() -> {
try { Thread.sleep(10000); } catch (Exception e) {}
});
inner.start();
try {
inner.join(2000);
} catch (InterruptedException e) {}
}, "JoinTimeoutThread");
// 方式4: LockSupport.parkNanos()
Thread t4 = new Thread(() -> {
java.util.concurrent.locks.LockSupport.parkNanos(1000000000L);
}, "ParkNanosThread");
t1.start();
t2.start();
t3.start();
t4.start();
Thread.sleep(100);
System.out.println("sleep() 状态: " + t1.getState()); // TIMED_WAITING
System.out.println("wait(timeout) 状态: " + t2.getState()); // TIMED_WAITING
System.out.println("join(timeout) 状态: " + t3.getState()); // TIMED_WAITING
System.out.println("parkNanos() 状态: " + t4.getState()); // TIMED_WAITING
}
}
6. TERMINATED(终止状态)
线程执行完毕或被强制终止
java
public class TerminatedStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行结束");
});
System.out.println("启动前: " + thread.getState()); // NEW
thread.start();
System.out.println("启动后: " + thread.getState()); // RUNNABLE
Thread.sleep(500);
System.out.println("执行中: " + thread.getState()); // TIMED_WAITING
thread.join();
System.out.println("结束后: " + thread.getState()); // TERMINATED
System.out.println("是否存活: " + thread.isAlive()); // false
// 注意:TERMINATED 状态的线程不能再次启动
// thread.start(); // 抛出 IllegalThreadStateException
}
}
完整的状态转换示例
java
public class CompleteStateTransitionDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread tracker = new Thread(() -> {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
// 1. RUNNABLE
printState(Thread.currentThread(), "执行中");
// 2. TIMED_WAITING (sleep)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
// 3. BLOCKED (等待锁)
synchronized (lock) {
System.out.println("获得锁");
}
// 4. WAITING (wait)
synchronized (lock) {
try {
lock.wait(500);
} catch (InterruptedException e) {}
}
System.out.println("线程结束");
}, "TargetThread");
// 监控线程状态变化
while (thread.getState() != Thread.State.TERMINATED) {
System.out.println("状态: " + thread.getState());
try {
Thread.sleep(200);
} catch (InterruptedException e) {}
}
});
tracker.start();
tracker.join();
}
private static void printState(Thread t, String action) {
System.out.println(action + " - 状态: " + t.getState());
}
}
状态转换条件表
| 当前状态 | 转换条件 | 目标状态 |
|---|---|---|
| NEW | start() |
RUNNABLE |
| RUNNABLE | 获得锁 | RUNNABLE |
| RUNNABLE | 释放CPU时间片 | RUNNABLE |
| RUNNABLE | sleep(time) |
TIMED_WAITING |
| RUNNABLE | wait() |
WAITING |
| RUNNABLE | wait(timeout) |
TIMED_WAITING |
| RUNNABLE | join() |
WAITING |
| RUNNABLE | join(timeout) |
TIMED_WAITING |
| RUNNABLE | LockSupport.park() |
WAITING |
| RUNNABLE | 尝试获取锁但被阻塞 | BLOCKED |
| RUNNABLE | run() 执行完成 |
TERMINATED |
| BLOCKED | 获得锁 | RUNNABLE |
| WAITING | notify()/notifyAll() |
BLOCKED → RUNNABLE |
| WAITING | LockSupport.unpark() |
RUNNABLE |
| TIMED_WAITING | 超时或被唤醒 | RUNNABLE |
| 任何状态 | 异常退出 | TERMINATED |
- NEW → RUNNABLE :只能通过
start(),不能重复启动 - RUNNABLE:Java中最复杂的状态,包含就绪和运行
- BLOCKED :只针对
synchronized锁,Lock锁不同 - WAITING:无限期等待,必须显式唤醒
- TIMED_WAITING:有限期等待,可自动退出
- TERMINATED:线程结束,不可复活
- 状态不能作为同步控制:状态会随时变化
补充:Java程序默认有多少线程?
java
// 导入 ManagementFactory 类,用于获取 JVM 的管理 Bean
import java.lang.management.ManagementFactory;
// 导入 ThreadMXBean 接口,用于监控线程系统
import java.lang.management.ThreadMXBean;
// 导入 ThreadInfo 类,包含线程的详细信息
import java.lang.management.ThreadInfo;
public class ThreadCountDemo {
public static void main(String[] args) {
// 1. 获取 JVM 的线程管理 Bean (ThreadMXBean 是 Java 提供的用于监控线程的底层接口)
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
// 2. 获取当前 JVM 中所有存活线程的 ID 数组
// 注意:因为线程是动态变化的,获取到的只是一个快照
long[] threadIds = threadBean.getAllThreadIds();
// 3. 打印当前 JVM 中的线程总数(即数组的长度)
System.out.println("当前JVM线程数:" + threadIds.length);
// 4. 遍历线程 ID 数组,逐个获取并打印线程信息
for (long id : threadIds) {
// 根据 ID 获取该线程的详细信息(快照)
// 【隐患提示】:在极短的时间内,某些线程可能已经执行完毕并销毁,
// 此时 getThreadInfo(id) 可能会返回 null。实际生产环境中建议进行 null 判断。
ThreadInfo info = threadBean.getThreadInfo(id);
// 打印线程名称(如 "main", "Reference-Handler" 等)以及线程当前的状态(如 RUNNABLE, WAITING 等)
// 【注意】:如果上面的 info 为 null,下面这行会抛出 NullPointerException
System.out.println(info.getThreadName() + " - " + info.getThreadState());
}
}
}
输出结果:
text
当前JVM线程数:6
Monitor Ctrl-Break - RUNNABLE
Attach Listener - RUNNABLE
Signal Dispatcher - RUNNABLE
Finalizer - WAITING
Reference Handler - WAITING
main - RUNNABLE
- main - RUNNABLE
是什么:这是你自己写的程序的主线程。
状态:RUNNABLE 表示它正在执行你的代码(当前正在执行打印输出语句)。 - Monitor Ctrl-Break - RUNNABLE
是什么:这是 IDE(如 IntelliJ IDEA 或 Eclipse)注入的后台线程。
作用:用于监听在控制台点击的"停止"按钮(红色方块)。如果在命令行直接用 java 命令运行这段代码,这个线程是不会出现的。
状态:RUNNABLE 表示它正在后台待命监听。 - Attach Listener - RUNNABLE
是什么:JVM 自带的通信监听线程。
作用:主要供外部工具(如 jconsole、jvisualvm、Arthas 等)连接到当前 JVM 时使用。当没有工具连接时,它通常处于休眠状态,这里显示 RUNNABLE 可能是刚好被唤醒或者处于等待事件的就绪状态。 - Signal Dispatcher - RUNNABLE
是什么:JVM 自带的信号分发线程。
作用:当操作系统给 JVM 发送信号时(比如你按了 Ctrl+C,或者外部工具发送了attach请求),这个线程负责把信号分发给对应的处理线程去处理。 - Finalizer - WAITING
是什么:JVM 自带的垃圾回收(GC)相关线程。
作用:专门负责执行对象的 finalize() 方法(也就是对象被回收前的最后清理工作)。
状态:WAITING(等待)。因为没有需要被清理的对象,所以它处于无限期等待状态,直到有对象被判定为可回收并被放入 finalize 队列中,它才会被唤醒。 - Reference Handler - WAITING
是什么:JVM 自带的垃圾回收(GC)相关线程。
作用:负责处理不同类型的引用(软引用、弱引用、虚引用)。当 GC 发现某个对象只被虚引用/弱引用指向时,会把这个引用丢到一个队列里,这个线程就是不断去队列里拿这些引用并做相应处理的。
状态:WAITING(等待)。同理,当前没有需要处理的引用,它在排队等待任务。