跳到主要内容Java 多线程状态详解:NEW、RUNNABLE、BLOCKED 等六种状态解析 | 极客日志Javajava算法
Java 多线程状态详解:NEW、RUNNABLE、BLOCKED 等六种状态解析
Java 线程的六种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),涵盖定义、触发原因、代码示例及易错点分析,帮助开发者理解线程生命周期,避免并发死锁与资源竞争问题。
云间运维4 浏览 Java 多线程状态详解
本文详细介绍 Java 线程的六种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),涵盖定义、触发原因、代码示例及易错点分析,帮助开发者理解线程生命周期,避免并发死锁与资源竞争问题。
线程状态概述
线程是程序执行的最小单位,其状态反映了线程在生命周期中的行为。理解这些状态对调试并发问题、避免死锁和提高性能至关重要。Java 中,线程状态通过 Thread.State 枚举表示,包括:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。每个状态都有特定触发条件和行为,下面我们逐一深入讲解。
1. NEW(新建)
定义与原因
NEW 状态表示线程对象已被创建(例如,通过 new Thread()),但尚未调用 start() 方法启动。此时,线程只是内存中的一个对象,没有分配系统资源(如 CPU 时间片),不会执行 run() 方法。只有调用 start() 后,线程才进入 RUNNABLE 状态。
代码案例
public class NewStateExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程运行中");
});
System.out.println("线程状态:" + thread.getState());
}
}
运行此代码,线程状态为 NEW,因为 start() 未被调用。
练习
练习 5:资源占用问题
在 NEW 状态下,线程是否占用系统资源(如 CPU 或内存)?编写代码创建 1000 个 NEW 线程,观察内存使用(可用 Runtime.getRuntime().totalMemory())。
public class ResourceTest {
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {});
}
System.out.println("内存占用:" + Runtime.getRuntime().totalMemory());
}
}
解释:NEW 线程占用内存(对象开销),但不占用 CPU。易错点:误以为 NEW 线程无开销,导致内存泄漏。
练习 4:错误场景分析
假设代码中多次调用 start() 方法(例如:thread.start(); thread.start();)。会发生什么?状态如何变化?
Thread thread = new Thread(() -> {});
thread.start();
thread.start();
解释:抛出 IllegalThreadStateException,因为线程不能重复启动。状态在第一次 start() 后变为 RUNNABLE 或 TERMINATED,但不会回到 NEW。易错点:忽略线程状态的生命周期,以为可以重复启动。
练习 3:状态转换模拟
创建一个线程对象,先打印状态(应为 NEW),然后调用 start(),再打印状态。观察变化。
Thread thread = new Thread(() -> {});
System.out.println("start 前状态:" + thread.getState());
thread.start();
System.out.println("start 后状态:" + thread.getState());
解释:调用 start() 后状态变为 RUNNABLE。易错点:在多线程环境中,状态打印可能受调度影响,不一定是即时变化。
练习 2:方法调用影响
在 NEW 状态下,线程能否调用 run() 方法?如果可以,调用后状态会变吗?编写代码测试。
Thread thread = new Thread(() -> {
System.out.println("run() 方法执行");
});
thread.run();
System.out.println("状态:" + thread.getState());
解释:直接调用 run() 不会改变状态(仍为 NEW),因为它只是普通方法调用,而非启动线程。易错点:混淆 run() 和 start(),以为 run() 能启动线程。
练习 1:基础状态检查
编写代码创建两个线程对象,但不调用 start()。打印它们的线程状态。确认状态是否为 NEW。
Thread t1 = new Thread(() -> {});
Thread t2 = new Thread(() -> {});
System.out.println("t1 状态:" + t1.getState());
System.out.println("t2 状态:" + t2.getState());
解释:状态应为 NEW。易错点:误以为线程创建后自动启动。
易错点与混淆点
- 易错点:NEW 状态容易被忽略,因为线程不执行任何任务。开发者可能在创建线程后忘记调用
start(),导致程序逻辑错误。
- 混淆点:NEW 状态与'未初始化'混淆。NEW 线程对象已初始化,但未激活。与 RUNNABLE 混淆,因为两者都涉及线程对象存在。
- 容易搞错的点:直接调用
run() 方法不会改变状态,它只是同步执行代码块,而非启动新线程。线程启动必须通过 start()。
2. RUNNABLE(可运行)
定义与原因
RUNNABLE 状态表示线程已启动(调用 start()),正在运行或等待 CPU 时间片。在 Java 中,RUNNABLE 统一表示就绪(Ready)和运行(Running)状态:就绪时线程等待调度,运行时占用 CPU 执行。操作系统层面,就绪和运行是分开的,但 Java API 只暴露 RUNNABLE。线程可通过 yield() 让出 CPU,但状态不变。
代码案例
public class RunnableStateExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
}
});
thread.start();
System.out.println("线程状态:" + thread.getState());
}
}
线程启动后进入 RUNNABLE 状态,可能在运行或就绪。
练习
练习 5:状态与性能
编写代码,测量线程在 RUNNABLE 状态下的 CPU 占用(可用 ThreadMXBean)。观察就绪和运行的区别。
import java.lang.management.ThreadMXBean;
import java.lang.management.ManagementFactory;
public class CpuUsage {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {}
});
thread.start();
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long cpuTime = bean.getThreadCpuTime(thread.getId());
System.out.println("CPU 时间:" + cpuTime);
System.out.println("状态:" + thread.getState());
}
}
解释:RUNNABLE 状态不区分就绪和运行,但 CPU 时间非零表示线程曾运行。易错点:混淆状态和实际 CPU 占用。
练习 4:多线程并发
启动 10 个线程,每个打印自己的状态。所有状态应为 RUNNABLE 吗?
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println("状态:" + Thread.currentThread().getState());
}).start();
}
解释:输出可能显示 RUNNABLE,但受调度影响,打印时线程可能在运行或就绪。易错点:在多核 CPU 上,多个线程可同时运行,状态打印可能不一致。
练习 3:yield() 方法影响
在线程中调用 Thread.yield(),打印状态。状态会变吗?
Thread thread = new Thread(() -> {
while (true) {
Thread.yield();
}
});
thread.start();
System.out.println("状态:" + thread.getState());
解释:状态仍为 RUNNABLE,yield() 只是提示调度器让出 CPU,不改变状态。易错点:误以为 yield() 使线程进入 WAITING。
练习 2:CPU 竞争模拟
创建两个高优先级线程,竞争 CPU。打印状态,观察调度。
Thread t1 = new Thread(() -> {
while (true) {}
});
Thread t2 = new Thread(() -> {
while (true) {}
});
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
System.out.println("t1 状态:" + t1.getState());
System.out.println("t2 状态:" + t2.getState());
解释:两者均为 RUNNABLE,但操作系统可能切换运行状态。易错点:以为优先级高的线程总在运行状态。
练习 1:状态打印时机
启动一个线程,在循环中打印状态。观察状态是否总为 RUNNABLE。
Thread thread = new Thread(() -> {
while (true) { }
});
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("状态:" + thread.getState());
Thread.sleep(100);
}
解释:状态始终为 RUNNABLE。易错点:误以为在循环中状态会变化,但 RUNNABLE 涵盖就绪和运行。
易错点与混淆点
- 易错点:RUNNABLE 状态在 Java 中统一表示,但开发者可能误以为线程总在运行,忽略就绪状态。导致性能优化时忽略调度开销。
- 混淆点:与 BLOCKED 混淆,因为两者都可能'等待',但 RUNNABLE 是主动等待 CPU,BLOCKED 是被动等待锁。
- 容易搞错的点:线程在 I/O 操作(如文件读写)时状态仍为 RUNNABLE,因为 I/O 在 Java 中可能不阻塞线程(使用 NIO 时)。操作系统层可能阻塞,但 Java 状态不变。
3. BLOCKED(阻塞)
定义与原因
BLOCKED 状态表示线程因等待监视器锁(如 synchronized 关键字)而阻塞。只有当线程试图进入 synchronized 代码块或方法,但锁被其他线程持有时,才进入此状态。一旦锁释放,线程自动转为 RUNNABLE。注意:BLOCKED 仅针对 synchronized 锁,不适用于其他锁机制(如 ReentrantLock)。
代码案例
public class BlockedStateExample {
public static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
while (true) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 获得锁");
}
});
t1.start();
t2.start();
System.out.println("t2 状态:" + t2.getState());
}
}
练习
练习 5:非 synchronized 锁
使用 ReentrantLock 代替 synchronized,线程在等待锁时状态是什么?
import java.util.concurrent.locks.ReentrantLock;
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
lock.lock();
while (true) {}
});
Thread t2 = new Thread(() -> {
lock.lock();
});
t1.start();
t2.start();
System.out.println("t2 状态:" + t2.getState());
解释:ReentrantLock 的等待使线程进入 WAITING 状态(通过 LockSupport.park()),而非 BLOCKED。易错点:以为所有锁机制都导致 BLOCKED 状态。
练习 4:死锁检测
创建死锁场景:两个线程互相等待锁。打印状态,识别 BLOCKED 线程。
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock1) {
synchronized (lock2) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
synchronized (lock1) {}
}
});
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("t1 状态:" + t1.getState());
System.out.println("t2 状态:" + t2.getState());
解释:两者均 BLOCKED,形成死锁。易错点:死锁时线程永久 BLOCKED,需外部干预。
练习 3:BLOCKED vs WAITING
使用 wait() 方法,线程状态是什么?编写代码对比。
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
});
t1.start();
Thread.sleep(100);
System.out.println("t1 状态:" + t1.getState());
解释:调用 wait() 进入 WAITING 状态,而非 BLOCKED。易错点:混淆 BLOCKED(锁竞争)和 WAITING(主动等待)。
练习 2:错误锁释放
线程在 BLOCKED 状态下,如何被唤醒?修改代码,让 t1 释放锁后观察 t2 状态变化。
解释:锁释放后,t2 自动转为 RUNNABLE。易错点:误以为需要显式唤醒(如 notify()),但 BLOCKED 是自动恢复。
练习 1:锁竞争
创建两个线程竞争同一锁。打印等待线程的状态。
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
}
});
t1.start();
t2.start();
Thread.sleep(100);
System.out.println("t2 状态:" + t2.getState());
解释:t2 在等待锁时状态为 BLOCKED。易错点:未确保 t1 先获得锁,导致状态打印错误。
易错点与混淆点
- 易错点:BLOCKED 状态只发生在
synchronized 锁竞争时。开发者可能在其他等待场景(如 I/O)误判为 BLOCKED。
- 混淆点:与 WAITING 状态混淆,因为两者都'等待',但 BLOCKED 是自动恢复的锁等待,WAITING 需要显式唤醒。
- 容易搞错的点:在
ReentrantLock 或 Condition 上等待时,状态是 WAITING 或 TIMED_WAITING,不是 BLOCKED。忽略这点会导致调试错误。
4. WAITING(等待)
定义与原因
WAITING 状态表示线程因调用无期限等待方法而暂停,例如 Object.wait()、Thread.join() 或 LockSupport.park()。线程进入此状态后,不会消耗 CPU 资源,必须由其他线程显式唤醒(如 notify() 或 unpark())。WAITING 常用于线程间协调,如生产者 - 消费者模型。
代码案例
public class WaitingStateExample {
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
});
thread.start();
Thread.sleep(100);
System.out.println("线程状态:" + thread.getState());
}
}
线程调用 wait() 后进入 WAITING 状态。
练习
练习 5:LockSupport.park()
使用 LockSupport.park() 使线程进入 WAITING。如何唤醒?
Thread thread = new Thread(() -> {
LockSupport.park();
});
thread.start();
Thread.sleep(100);
System.out.println("状态:" + thread.getState());
LockSupport.unpark(thread);
解释:park() 进入 WAITING,unpark() 唤醒。易错点:unpark() 可在 park() 前调用,防止永久等待。
练习 4:多线程协调
实现生产者 - 消费者模型,生产者 wait() 当缓冲区满。打印状态确认 WAITING。
import java.util.Queue;
import java.util.LinkedList;
Queue<Integer> buffer = new LinkedList<>();
int maxSize = 1;
Object lock = new Object();
Thread producer = new Thread(() -> {
synchronized (lock) {
while (buffer.size() == maxSize) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
buffer.add(1);
lock.notify();
}
});
producer.start();
解释:当缓冲区满时,生产者进入 WAITING。易错点:未在循环中检查条件(虚假唤醒问题)。
练习 3:永久等待风险
编写代码,线程 wait() 但无唤醒逻辑。状态会变吗?如何避免死锁?
Object lock = new Object();
Thread t = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
});
t.start();
Thread.sleep(2000);
System.out.println("状态:" + t.getState());
解释:状态永久 WAITING,导致线程泄漏。易错点:忘记设计唤醒机制。
练习 2:join() 方法
主线程调用子线程的 join(),状态是什么?
Thread child = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
});
child.start();
child.join();
System.out.println("主线程状态:" + Thread.currentThread().getState());
解释:join() 使调用线程(主线程)进入 WAITING 状态,等待子线程结束。但打印自身状态时为 RUNNABLE,因为 join() 内部实现使用 WAITING。易错点:误以为 join() 改变的是子线程状态。
练习 1:wait() 方法使用
线程调用 wait() 后,状态如何?编写代码唤醒它,观察状态变化。
Object lock = new Object();
Thread t = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {}
System.out.println("唤醒后");
}
});
t.start();
Thread.sleep(100);
System.out.println("状态:" + t.getState());
synchronized (lock) {
lock.notify();
}
Thread.sleep(100);
System.out.println("状态:" + t.getState());
解释:唤醒后状态变为 RUNNABLE。易错点:忘记在同步块内调用 notify(),导致唤醒失败。
易错点与混淆点
- 易错点:WAITING 状态需要显式唤醒,开发者可能忘记调用
notify() 或 unpark(),导致线程永久挂起。
- 混淆点:与 BLOCKED 混淆,因为两者都'等待',但 WAITING 是主动调用等待方法,BLOCKED 是锁竞争被动等待。
- 容易搞错的点:
Thread.join() 使调用线程进入 WAITING,而非被 join 的线程。忽略这点会导致状态判断错误。
5. TIMED_WAITING(计时等待)
定义与原因
TIMED_WAITING 状态表示线程因调用有时间限制的等待方法而暂停,例如 Thread.sleep(long millis)、Object.wait(long timeout) 或 Thread.join(long millis)。线程在指定时间后自动唤醒(或提前被中断),无需显式唤醒。常用于定时任务或超时控制。
代码案例
public class TimedWaitingStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
});
thread.start();
Thread.sleep(100);
System.out.println("线程状态:" + thread.getState());
}
}
线程 sleep() 时进入 TIMED_WAITING 状态。
练习
练习 5:定时任务模拟
创建线程每秒执行任务,使用 sleep(1000)。打印状态确认 TIMED_WAITING。
Thread timer = new Thread(() -> {
while (true) {
System.out.println("任务执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
});
timer.start();
Thread.sleep(1500);
System.out.println("状态:" + timer.getState());
解释:在 sleep() 期间状态为 TIMED_WAITING。易错点:循环中使用 sleep() 可能导致精度问题。
练习 4:中断处理
线程在 TIMED_WAITING 时被中断(interrupt()),状态如何?编写代码测试。
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("中断捕获");
}
});
t.start();
Thread.sleep(100);
t.interrupt();
Thread.sleep(100);
System.out.println("状态:" + t.getState());
解释:中断后线程唤醒,状态变为 RUNNABLE。易错点:未处理 InterruptedException,导致行为未定义。
练习 3:join(timeout)
主线程调用子线程的 join(1000),状态变化?
Thread child = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
});
child.start();
child.join(1000);
System.out.println("主线程状态:" + Thread.currentThread().getState());
解释:join(timeout) 使调用线程进入 TIMED_WAITING,但打印自身状态时为 RUNNABLE。易错点:混淆超时和实际线程结束。
练习 2:wait(timeout)
使用 wait(500),状态如何?与 sleep() 对比。
Object lock = new Object();
Thread t = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(500);
} catch (InterruptedException e) {}
}
});
t.start();
Thread.sleep(100);
System.out.println("状态:" + t.getState());
解释:状态为 TIMED_WAITING。易错点:wait(timeout) 必须在同步块内调用,否则抛 IllegalMonitorStateException。
练习 1:sleep() 方法
线程调用 sleep(1000),打印状态。时间结束后状态变化?
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
});
t.start();
Thread.sleep(100);
System.out.println("状态:" + t.getState());
Thread.sleep(2000);
System.out.println("状态:" + t.getState());
解释:睡眠结束后状态变为 TERMINATED(如果 run() 结束)。易错点:单位错误(sleep 参数是毫秒)。
易错点与混淆点
- 易错点:时间参数单位错误(如秒 vs 毫秒),导致等待时间不符预期。
sleep() 不会释放锁,而 wait(timeout) 会。
- 混淆点:与 WAITING 混淆,因为两者都'等待',但 TIMED_WAITING 有超时机制,自动唤醒。
- 容易搞错的点:
Thread.join(timeout) 如果超时,线程未结束,调用线程仍继续执行,但被 join 的线程可能还在运行。
6. TERMINATED(终止)
定义与原因
TERMINATED 状态表示线程已执行完毕(run() 方法正常结束)或因未捕获异常退出。线程进入此状态后,不能被重启(调用 start() 会抛异常),系统资源被释放。线程对象仍存在,但状态不可变。
代码案例
public class TerminatedStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
});
thread.start();
thread.join();
System.out.println("线程状态:" + thread.getState());
}
}
练习
练习 5:线程池影响
在线程池中,线程执行任务后状态如何?(模拟使用 ExecutorService)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(() -> {});
pool.shutdown();
解释:线程池线程任务结束后可能回到 RUNNABLE(等待新任务),不直接 TERMINATED。易错点:以为所有线程结束时都 TERMINATED,忽略线程池机制。
练习 4:isAlive() 方法
使用 isAlive() 检查线程是否存活。终止后返回什么?
Thread t = new Thread(() -> {});
t.start();
t.join();
System.out.println("isAlive: " + t.isAlive());
System.out.println("状态:" + t.getState());
解释:isAlive() 返回 false,状态为 TERMINATED。易错点:混淆 isAlive() 和状态 API。
练习 3:重启尝试
线程终止后,调用 start() 方法。会发生什么?
Thread t = new Thread(() -> {});
t.start();
t.join();
t.start();
解释:抛出 IllegalThreadStateException。易错点:误以为线程可重用。
练习 2:异常终止
线程抛出未捕获异常,状态如何?
Thread t = new Thread(() -> {
throw new RuntimeException("错误");
});
t.start();
t.join();
System.out.println("状态:" + t.getState());
解释:未捕获异常导致 TERMINATED。易错点:忽略异常处理,线程静默终止。
练习 1:正常结束
线程执行简单任务后结束。打印状态。
Thread t = new Thread(() -> {});
t.start();
t.join();
System.out.println("状态:" + t.getState());
解释:状态为 TERMINATED。易错点:未使用 join() 确保线程结束,导致状态打印过早。
易错点与混淆点
- 易错点:线程终止后仍尝试操作(如
start()),导致异常。开发者可能未处理线程异常,导致意外终止。
- 混淆点:与 RUNNABLE 混淆,因为线程结束前可能短暂处于 RUNNABLE。
isAlive() 方法更直接判断存活。
- 容易搞错的点:在线程池中,线程对象被重用,状态不固定;直接创建线程时,TERMINATED 是最终状态。
总结与常见混淆点对比
线程状态反映了并发编程的核心行为。以下是关键混淆点总结:
- RUNNABLE vs BLOCKED:RUNNABLE 是主动等待 CPU,BLOCKED 是被动等待锁。I/O 阻塞在 Java 中可能不改变状态。
- BLOCKED vs WAITING:BLOCKED 只针对
synchronized 锁,自动恢复;WAITING 需要显式唤醒,适用于 wait() 或 park()。
- WAITING vs TIMED_WAITING:WAITING 无超时,TIMED_WAITING 有超时自动唤醒。
sleep() 是 TIMED_WAITING,但不会释放锁。
- TERMINATED:线程结束后不可重启,
isAlive() 为 false。
本文旨在帮助读者深入理解 Java 线程状态机制。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online