Java 线程状态与线程组
一、Java 线程状态详解
1. 线程状态定义与转换机制
1.1 线程状态的官方定义
Java 线程在其生命周期中会经历多种状态,这些状态在 Thread.State
枚举类中被明确定义。正确理解线程状态及状态转换规律,是排查并发问题的关键。
根据 Java 官方文档,线程共有 6 种状态:
新建状态(NEW):
- 当线程对象被创建但尚未调用
start()
方法时,线程处于新建状态 - 此时线程尚未开始执行,仅存在于内存中
- 示例:
Thread t = new Thread();
创建后但未启动时
- 当线程对象被创建但尚未调用
运行状态(RUNNABLE):
- 调用
start()
方法后,线程进入运行状态 - 该状态包含两种情况:
- 线程正在 CPU 上执行(Running)
- 线程处于就绪状态,等待 CPU 调度(Ready)
- 在 Java 中,这两种情况统一归为 RUNNABLE 状态
- 这是线程的主要工作状态
- 调用
阻塞状态(BLOCKED):
- 线程在竞争同步锁时失败,会进入阻塞状态
- 典型场景:多个线程竞争
synchronized
锁 - 当其他线程释放锁并允许当前线程获取锁时,该线程会从 BLOCKED 状态转换为 RUNNABLE 状态
- 示例:线程A和线程B同时访问
synchronized
方法,线程A获得锁,线程B进入BLOCKED状态
等待状态(WAITING):
- 线程通过调用
Object.wait()
、Thread.join()
或LockSupport.park()
等方法进入等待状态 - 处于等待状态的线程需要其他线程显式唤醒(如调用
Object.notify()
)才能转换为 RUNNABLE 状态 - 等待期间不会被分配 CPU 时间片
- 常用于线程间协调
- 线程通过调用
超时等待状态(TIMED_WAITING):
- 线程通过调用带有超时参数的方法(如
Thread.sleep(long)
、Object.wait(long)
、Thread.join(long)
等)进入该状态 - 与 WAITING 状态不同,超时等待状态的线程会在指定时间后自动唤醒
- 无需其他线程显式唤醒
- 常用于定时任务或需要超时控制的场景
- 线程通过调用带有超时参数的方法(如
终止状态(TERMINATED):
- 线程执行完
run()
方法或因异常退出时,进入终止状态 - 一旦线程终止,就无法再恢复到其他状态
- 终止后的线程对象可以被垃圾回收
- 线程执行完
1.2 线程状态转换机制
线程状态之间的转换遵循特定规则,以下是常见的状态转换路径:
NEW → RUNNABLE:
- 调用
start()
方法后,线程由新建状态进入运行状态 - 注意:
start()
方法只能调用一次,多次调用会抛出IllegalThreadStateException
- 示例:
Thread t = new Thread(() -> System.out.println("Running")); t.start(); // 状态从NEW变为RUNNABLE
- 调用
RUNNABLE → BLOCKED:
- 当线程尝试获取同步锁(如进入
synchronized
代码块)但该锁被其他线程持有时,会进入阻塞状态 - 示例:
synchronized(lock) {// 如果其他线程持有锁,当前线程会进入BLOCKED状态 }
- 当线程尝试获取同步锁(如进入
RUNNABLE → WAITING:
- 线程调用无参
Object.wait()
、Thread.join()
或LockSupport.park()
方法时,会从运行状态进入等待状态 - 示例:
synchronized(lock) {lock.wait(); // 进入WAITING状态 }
- 线程调用无参
RUNNABLE → TIMED_WAITING:
- 调用带有超时参数的方法(如
Thread.sleep(1000)
、Object.wait(1000)
等)时,线程进入超时等待状态 - 示例:
Thread.sleep(1000); // 进入TIMED_WAITING状态
- 调用带有超时参数的方法(如
BLOCKED/WAITING/TIMED_WAITING → RUNNABLE:
- 当线程获取到同步锁(BLOCKED 状态)、被其他线程唤醒(WAITING 状态)或超时时间结束(TIMED_WAITING 状态)时,会转换为 RUNNABLE 状态,等待 CPU 调度
- 示例:
synchronized(lock) {lock.notify(); // 唤醒一个WAITING状态的线程// 或者等待时间到期 }
RUNNABLE → TERMINATED:
- 线程执行完
run()
方法或因未捕获的异常终止时,进入终止状态 - 示例:
Thread t = new Thread(() -> {System.out.println("Thread running");// run方法执行完毕,线程进入TERMINATED状态 }); t.start();
- 线程执行完
1.3 线程状态相关注意事项
避免线程状态误判:
Thread.isAlive()
方法返回true
表示线程处于 RUNNABLE、BLOCKED、WAITING 或 TIMED_WAITING 状态- 返回
false
表示处于 NEW 或 TERMINATED 状态 - 不能仅通过该方法判断线程是否在运行中
sleep() 与 wait() 的区别:
Thread.sleep(long)
会使线程进入 TIMED_WAITING 状态,但不会释放同步锁Object.wait()
会释放同步锁,且需要在synchronized
代码块中调用- 示例对比:
// sleep() 示例 synchronized(lock) {Thread.sleep(1000); // 持有锁进入休眠 }// wait() 示例 synchronized(lock) {lock.wait(); // 释放锁并等待 }
中断对线程状态的影响:
- 调用
Thread.interrupt()
方法会设置线程的中断标志,但不会直接终止线程 - 如果线程处于 WAITING 或 TIMED_WAITING 状态,会抛出
InterruptedException
并清除中断标志 - 正确处理中断的示例:
try {Thread.sleep(1000); } catch (InterruptedException e) {// 恢复中断状态Thread.currentThread().interrupt();// 处理中断逻辑 }
- 调用
线程状态监控:
- 可以通过
Thread.getState()
方法获取线程当前状态 - 调试工具(如JVisualVM)可以显示线程状态图
- 生产环境监控时,线程状态分析是诊断性能问题的重要手段
- 可以通过
线程池中的线程状态:
- 线程池中的工作线程可能会在 RUNNABLE 和 WAITING/TIMED_WAITING 之间频繁切换
- 当没有任务时,线程池中的线程通常处于 WAITING 状态(等待新任务)
- 配置合理的线程池参数可以优化线程状态转换开销
二、Java 线程组详解
1. 线程组概述
线程组(ThreadGroup)是 Java 中用于管理一组线程的数据结构,它可以对多个线程进行统一操作,如设置优先级、中断所有线程等。线程组本身也可以包含其他线程组,形成树形结构。这种设计提供了一种层次化的线程管理方式,使得开发者能够对相关线程进行批量操作。
2. 线程组的特性
2.1 树形结构
- 每个线程组可以包含多个线程和子线程组
- Java 虚拟机启动时会创建系统线程组(system)
- 用户创建的线程组默认父线程组是当前线程所在的线程组
- 示例:主线程默认属于"main"线程组,创建的新线程组默认继承该组
2.2 优先级继承
- 线程组有默认优先级(默认为Thread.NORM_PRIORITY)
- 新创建的线程若未指定优先级,会继承所在线程组的优先级
- 线程优先级不能超过其所在线程组的最大优先级
- 示例:若线程组设置最大优先级为5,则其中的线程最高只能设置为5
2.3 统一操作
- 可以通过线程组对其包含的所有线程执行批量操作
- 常用批量操作包括:
interrupt()
:中断所有线程setMaxPriority(int)
:设置最大优先级suspend()
/resume()
(已废弃):挂起/恢复所有线程
- 示例场景:在应用程序关闭时,可以中断线程组中的所有线程
3. 线程组的常用操作
3.1 创建线程组
// 创建新线程组(父线程组为当前线程所在组)
ThreadGroup group = new ThreadGroup("MyGroup");// 创建子线程组并指定父线程组
ThreadGroup parentGroup = new ThreadGroup("ParentGroup");
ThreadGroup subGroup = new ThreadGroup(parentGroup, "SubGroup");
3.2 创建线程时指定线程组
ThreadGroup group = new ThreadGroup("WorkerGroup");// 创建属于指定线程组的线程
Thread worker1 = new Thread(group, () -> {// 线程任务代码
}, "Worker-1");Thread worker2 = new Thread(group, () -> {// 线程任务代码
}, "Worker-2");
3.3 获取线程组信息
ThreadGroup group = Thread.currentThread().getThreadGroup();// 获取线程组名称
String groupName = group.getName();// 获取活跃线程数(估计值)
int activeThreads = group.activeCount();// 获取活跃子线程组数
int activeGroups = group.activeGroupCount();// 打印线程组信息(调试用)
group.list();
3.4 线程组批量操作
ThreadGroup group = new ThreadGroup("TaskGroup");// 设置线程组的最大优先级
group.setMaxPriority(Thread.MAX_PRIORITY - 1);// 中断线程组中所有线程
group.interrupt();// 销毁空线程组(必须确保没有活跃线程和子线程组)
if (group.activeCount() == 0 && group.activeGroupCount() == 0) {group.destroy();
}
4. 线程组的注意事项
4.1 线程组的安全性问题
- ThreadGroup中的多数方法没有同步机制
- 多线程操作同一个线程组时需要额外的同步措施
- 示例问题:在迭代线程组中的线程列表时,其他线程可能正在修改该列表
4.2 不建议过度使用线程组
- 线程组更多是为了早期版本的兼容性而保留
- 现代并发编程更推荐使用Executor框架和线程池
- 线程池(如ThreadPoolExecutor)提供了更强大的线程管理功能
- 示例替代方案:使用ExecutorService管理线程生命周期
4.3 线程组的异常处理
ThreadGroup customGroup = new ThreadGroup("CustomGroup") {@Overridepublic void uncaughtException(Thread t, Throwable e) {// 自定义异常处理逻辑System.err.println("Thread " + t.getName() + " threw exception: " + e);// 可以记录日志、发送通知等}
};
4.4 线程组的销毁限制
destroy()
方法只能销毁空线程组- 如果线程组包含活跃线程或子线程组,会抛出IllegalThreadStateException
- 销毁后的线程组不能再添加线程或子线程组
- 最佳实践:在调用destroy()前检查线程组状态
if (group.activeCount() == 0 && group.activeGroupCount() == 0) {group.destroy();
} else {// 处理未销毁的情况
}