天选集结号活动中心

天选集结号活动中心

shape
  • Home
  • 展会直击
  • Java并发编程:线程状态全解析v

Java并发编程:线程状态全解析v

  • 2026-08-18 03:16:04
  • admin

1、是什么:线程状态的核心定义与特征

Java中的线程状态(Thread State)是指线程从创建到销毁的整个生命周期中所处的不同阶段,JVM通过java.lang.Thread.State枚举类明确定义了6种核心状态,每种状态对应线程生命周期中一个明确的行为阶段,其核心内涵和关键特征如下:

状态名称

定义

关键特征

NEW(新建)

线程对象已创建,但未调用start()方法

仅完成对象实例化,未分配CPU执行资源,不会被操作系统调度

RUNNABLE(可运行)

调用start()后,线程处于“就绪”或“运行中”状态(JVM层面合并这两种状态)

就绪:等待CPU调度;运行中:正在执行run()方法;可被操作系统切换状态

BLOCKED(阻塞)

线程因等待获取同步锁(synchronized)而被挂起

仅与同步锁竞争相关,获取锁后会回到RUNNABLE状态

WAITING(等待)

线程无时限等待其他线程的特定操作(如notify()、join())

无超时时间,必须由其他线程主动唤醒,否则永久等待

TIMED_WAITING(计时等待)

线程有超时时间的等待(如sleep(ms)、wait(ms))

超时后会自动唤醒,无需其他线程干预

TERMINATED(终止)

线程的run()方法执行完毕,或因异常终止

状态不可逆,无法重新启动(调用start()会抛IllegalThreadStateException)

核心特征总结:线程状态是单向不可逆的(除RUNNABLE可切换到BLOCKED/WAITING/TIMED_WAITING后再回到RUNNABLE),且所有状态最终都会走向TERMINATED。

2、为什么需要:学习线程状态的必要性与价值

核心痛点解决

问题1:并发程序调试困难,无法定位“线程卡死”“锁竞争”“无限等待”等问题 → 线程状态是排查并发问题的“诊断依据”(如BLOCKED状态说明锁竞争,WAITING状态说明可能缺少唤醒操作)。

问题2:资源浪费(如线程无意义等待导致CPU/内存占用) → 理解状态切换逻辑可优化线程调度,减少无效等待。

问题3:程序行为不可控(如重复启动线程、错误唤醒线程) → 掌握状态规则可避免非法操作,保证线程生命周期可控。

实际应用价值

开发层面:编写高可靠的并发代码(如避免线程永久等待、合理使用超时等待)。

调试层面:通过jstack、IDE调试工具查看线程状态,快速定位并发BUG(如死锁时线程处于BLOCKED状态)。

优化层面:根据线程状态分布优化锁设计、线程池参数(如减少BLOCKED线程数量,提升程序并发效率)。

3、核心工作模式:线程状态的运作逻辑与关键要素

核心运作逻辑

线程状态的切换本质是JVM+操作系统共同调度的结果:JVM定义状态分类,操作系统负责实际的线程调度(如CPU分配、阻塞唤醒)。

关键要素及关联

关键要素

作用

与状态的关联

Thread对象

线程的载体,存储状态信息

通过thread.getState()可获取当前状态

CPU调度器

分配CPU执行权

决定RUNNABLE状态中“就绪”与“运行中”的切换

同步锁(synchronized/Lock)

控制线程对共享资源的访问

锁竞争导致线程进入BLOCKED状态,获取锁后回到RUNNABLE

等待/唤醒机制(wait()/notify()、LockSupport)

线程间通信的核心手段

触发线程在RUNNABLE↔WAITING/TIMED_WAITING之间切换

超时机制(sleep(ms)、wait(ms))

控制等待的时间范围

区分WAITING(无超时)和TIMED_WAITING(有超时)状态

核心机制

状态切换触发规则:所有状态切换必须通过特定方法调用或系统事件触发(如调用sleep()进入TIMED_WAITING,锁释放触发BLOCKED线程唤醒)。

状态可见性:线程状态的变更通过JVM的内存模型保证可见性,可通过getState()实时获取(但需注意:状态获取是“瞬时值”,可能获取后状态已变更)。

4、工作流程:线程状态的完整切换链路

流程图(Mermaid语法)

graph TD

A[NEW 新建] -- 调用start() --> B[RUNNABLE 可运行]

B -- 竞争锁失败 --> C[BLOCKED 阻塞]

C -- 获取锁成功 --> B

B -- wait/join/park --> D[WAITING 等待]

D -- notify/unpark --> B

B -- sleep/wait/join超时 --> E[TIMED_WAITING]

E -- 超时/唤醒 --> B

B -- 运行结束 --> F[TERMINATED]

D -- 异常 --> F

E -- 异常 --> F

C -- 异常 --> F

完整工作流程步骤

初始化阶段:创建Thread对象(如new Thread(()->{})),线程进入NEW状态,此时仅完成对象实例化,未与操作系统的线程关联。

启动阶段:调用thread.start()方法,JVM向操作系统申请创建线程,线程进入RUNNABLE状态(分为“就绪”和“运行中”):

就绪:线程等待CPU调度,未执行run()方法;

运行中:CPU分配执行权,线程执行run()方法。

状态切换阶段(RUNNABLE为核心切换节点):

切换到BLOCKED:线程尝试获取synchronized锁,若锁被占用则进入BLOCKED,直到锁释放后重新竞争,成功则回到RUNNABLE;

切换到WAITING:调用wait()(无参)、join()(无参)、LockSupport.park()等方法,线程放弃CPU和锁,进入无时限等待,需其他线程调用notify()/unpark()唤醒后回到RUNNABLE;

切换到TIMED_WAITING:调用sleep(1000)、wait(1000)、join(1000)等带超时参数的方法,线程进入限时等待,超时后自动唤醒,或被提前唤醒,回到RUNNABLE。

终止阶段:

正常终止:run()方法执行完毕,线程进入TERMINATED;

异常终止:run()方法抛出未捕获的异常,线程直接进入TERMINATED;

注意:TERMINATED状态的线程无法再次调用start(),否则抛IllegalThreadStateException。

5、入门实操:线程状态的可视化验证

实操目标

通过代码验证6种线程状态的切换过程,直观理解每种状态的触发条件。

完整实操代码

public class ThreadStateDemo {

// 用于锁竞争的对象

private static final Object LOCK = new Object();

public static void main(String[] args) throws InterruptedException {

// 1. 验证NEW状态

Thread newThread = new Thread(() -> {});

System.out.println("1. newThread状态:" + newThread.getState()); // NEW

// 2. 验证RUNNABLE状态

Thread runnableThread = new Thread(() -> {

// 循环执行,让线程处于RUNNABLE(运行中)

while (true) {}

});

runnableThread.start();

Thread.sleep(100); // 等待线程启动

System.out.println("2. runnableThread状态:" + runnableThread.getState()); // RUNNABLE

// 3. 验证BLOCKED状态

// 先启动一个线程占用锁

new Thread(() -> {

synchronized (LOCK) {

try {

Thread.sleep(2000); // 占用锁2秒

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}).start();

Thread.sleep(100); // 等待锁被占用

// 第二个线程尝试获取锁,进入BLOCKED

Thread blockedThread = new Thread(() -> {

synchronized (LOCK) {} // 竞争锁

});

blockedThread.start();

Thread.sleep(100);

System.out.println("3. blockedThread状态:" + blockedThread.getState()); // BLOCKED

// 4. 验证WAITING状态

Thread waitingThread = new Thread(() -> {

synchronized (LOCK) {

try {

LOCK.wait(); // 无参wait,进入WAITING

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

});

waitingThread.start();

Thread.sleep(100);

System.out.println("4. waitingThread状态:" + waitingThread.getState()); // WAITING

// 5. 验证TIMED_WAITING状态

Thread timedWaitingThread = new Thread(() -> {

try {

Thread.sleep(1000); // 计时等待1秒

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

});

timedWaitingThread.start();

Thread.sleep(100);

System.out.println("5. timedWaitingThread状态:" + timedWaitingThread.getState()); // TIMED_WAITING

// 6. 验证TERMINATED状态

Thread terminatedThread = new Thread(() -> {});

terminatedThread.start();

Thread.sleep(100); // 等待线程执行完毕

System.out.println("6. terminatedThread状态:" + terminatedThread.getState()); // TERMINATED

// 关闭运行中的线程(避免程序无法退出)

runnableThread.interrupt();

// 唤醒WAITING线程(避免资源泄漏)

synchronized (LOCK) {

LOCK.notifyAll();

}

}

}

实操步骤与注意事项

步骤

复制上述代码到Java项目中,运行main方法;

查看控制台输出,验证每种状态的触发结果;

可通过IDE的调试模式,断点查看线程状态的实时变化。

注意事项

getState()获取的是瞬时状态:如TIMED_WAITING线程可能在获取状态后已超时,显示为RUNNABLE,需通过Thread.sleep()短暂等待保证状态准确性;

避免永久等待:WAITING状态的线程必须手动唤醒(如notifyAll()),否则程序退出前会一直占用资源;

中断处理:interrupt()可终止无限循环的RUNNABLE线程,避免程序无法退出;

锁的释放:synchronized块中的wait()会释放锁,而sleep()不会释放锁,需注意锁竞争逻辑。

6、常见问题及解决方案

问题1:重复启动线程导致IllegalThreadStateException

现象

调用TERMINATED或RUNNABLE状态线程的start()方法,抛出IllegalThreadStateException。

原因

线程状态不可逆,NEW状态的线程调用start()后,无论后续进入何种状态,都无法再次启动。

解决方案

避免重复调用start():通过状态判断(thread.getState() == Thread.State.NEW)确保仅启动一次;

如需重复执行任务:创建新的Thread对象,而非复用旧对象。

// 正确写法:先判断状态再启动

if (thread.getState() == Thread.State.NEW) {

thread.start();

} else {

// 重新创建线程执行任务

Thread newThread = new Thread(thread.getRunnable());

newThread.start();

}

问题2:线程进入WAITING状态后无法唤醒(永久等待)

现象

调用wait()/join()后,线程一直处于WAITING状态,程序卡死。

原因

未调用notify()/notifyAll()唤醒线程;

唤醒操作(notify())在wait()之前执行,导致唤醒信号丢失;

唤醒时未持有对应的锁(notify()必须在synchronized块中执行)。

解决方案

保证唤醒操作在wait()之后执行:通过同步机制(如标志位)控制执行顺序;

唤醒时必须持有锁:notify()/notifyAll()需在synchronized (LOCK)块中调用;

优先使用notifyAll()而非notify():避免唤醒错线程(notify()随机唤醒一个等待线程)。

// 正确的等待-唤醒逻辑

synchronized (LOCK) {

while (!isReady) { // 用while循环判断条件,避免虚假唤醒

LOCK.wait();

}

}

// 唤醒线程(必须在synchronized块中)

synchronized (LOCK) {

isReady = true;

LOCK.notifyAll();

}

问题3:线程大量处于BLOCKED状态,并发效率低

现象

通过jstack查看线程状态,大量线程处于BLOCKED,程序响应慢。

原因

synchronized锁粒度太大(如锁住整个方法/类),导致锁竞争激烈;

锁持有时间过长(如在锁内执行IO、睡眠等耗时操作)。

解决方案

减小锁粒度:仅锁住共享资源的操作,而非整个业务逻辑;

缩短锁持有时间:将耗时操作(如IO、网络请求)移出synchronized块;

替换锁类型:使用java.util.concurrent.locks.ReentrantLock,支持公平锁/非公平锁、可中断锁,减少阻塞时间。

// 优化前:锁粒度大,持有时间长

synchronized (this) {

readDataFromDB(); // 耗时DB操作

updateSharedVar(); // 共享变量更新

}

// 优化后:仅锁住共享变量更新,减小锁粒度

readDataFromDB(); // 移出锁外

synchronized (this) {

updateSharedVar();

}

总结

Java线程有6种核心状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),状态切换由特定方法或系统事件触发,且不可逆(除RUNNABLE与阻塞/等待状态的切换);

学习线程状态是排查并发问题、优化程序性能的核心基础,可通过getState()或jstack工具查看状态;

常见问题集中在:重复启动线程、永久等待、锁竞争导致大量BLOCKED线程,解决方案核心是遵循状态规则、优化锁设计、保证等待-唤醒逻辑正确。

<<<
Previous Post
郑州创奇教育

Copyright © 2088 天选集结号活动中心 All Rights Reserved.

友情链接