当前位置: 首页 > news >正文

Java 线程状态与线程组

一、Java 线程状态详解

1. 线程状态定义与转换机制

1.1 线程状态的官方定义

Java 线程在其生命周期中会经历多种状态,这些状态在 Thread.State 枚举类中被明确定义。正确理解线程状态及状态转换规律,是排查并发问题的关键。

根据 Java 官方文档,线程共有 6 种状态:

  1. 新建状态(NEW)

    • 当线程对象被创建但尚未调用 start() 方法时,线程处于新建状态
    • 此时线程尚未开始执行,仅存在于内存中
    • 示例:Thread t = new Thread(); 创建后但未启动时
  2. 运行状态(RUNNABLE)

    • 调用 start() 方法后,线程进入运行状态
    • 该状态包含两种情况:
      • 线程正在 CPU 上执行(Running)
      • 线程处于就绪状态,等待 CPU 调度(Ready)
    • 在 Java 中,这两种情况统一归为 RUNNABLE 状态
    • 这是线程的主要工作状态
  3. 阻塞状态(BLOCKED)

    • 线程在竞争同步锁时失败,会进入阻塞状态
    • 典型场景:多个线程竞争 synchronized
    • 当其他线程释放锁并允许当前线程获取锁时,该线程会从 BLOCKED 状态转换为 RUNNABLE 状态
    • 示例:线程A和线程B同时访问 synchronized 方法,线程A获得锁,线程B进入BLOCKED状态
  4. 等待状态(WAITING)

    • 线程通过调用 Object.wait()Thread.join()LockSupport.park() 等方法进入等待状态
    • 处于等待状态的线程需要其他线程显式唤醒(如调用 Object.notify())才能转换为 RUNNABLE 状态
    • 等待期间不会被分配 CPU 时间片
    • 常用于线程间协调
  5. 超时等待状态(TIMED_WAITING)

    • 线程通过调用带有超时参数的方法(如 Thread.sleep(long)Object.wait(long)Thread.join(long) 等)进入该状态
    • 与 WAITING 状态不同,超时等待状态的线程会在指定时间后自动唤醒
    • 无需其他线程显式唤醒
    • 常用于定时任务或需要超时控制的场景
  6. 终止状态(TERMINATED)

    • 线程执行完 run() 方法或因异常退出时,进入终止状态
    • 一旦线程终止,就无法再恢复到其他状态
    • 终止后的线程对象可以被垃圾回收

1.2 线程状态转换机制

线程状态之间的转换遵循特定规则,以下是常见的状态转换路径:

  1. NEW → RUNNABLE

    • 调用 start() 方法后,线程由新建状态进入运行状态
    • 注意:start() 方法只能调用一次,多次调用会抛出 IllegalThreadStateException
    • 示例:
      Thread t = new Thread(() -> System.out.println("Running"));
      t.start();  // 状态从NEW变为RUNNABLE
      

  2. RUNNABLE → BLOCKED

    • 当线程尝试获取同步锁(如进入 synchronized 代码块)但该锁被其他线程持有时,会进入阻塞状态
    • 示例:
      synchronized(lock) {// 如果其他线程持有锁,当前线程会进入BLOCKED状态
      }
      

  3. RUNNABLE → WAITING

    • 线程调用无参 Object.wait()Thread.join()LockSupport.park() 方法时,会从运行状态进入等待状态
    • 示例:
      synchronized(lock) {lock.wait();  // 进入WAITING状态
      }
      

  4. RUNNABLE → TIMED_WAITING

    • 调用带有超时参数的方法(如 Thread.sleep(1000)Object.wait(1000) 等)时,线程进入超时等待状态
    • 示例:
      Thread.sleep(1000);  // 进入TIMED_WAITING状态
      

  5. BLOCKED/WAITING/TIMED_WAITING → RUNNABLE

    • 当线程获取到同步锁(BLOCKED 状态)、被其他线程唤醒(WAITING 状态)或超时时间结束(TIMED_WAITING 状态)时,会转换为 RUNNABLE 状态,等待 CPU 调度
    • 示例:
      synchronized(lock) {lock.notify();  // 唤醒一个WAITING状态的线程// 或者等待时间到期
      }
      

  6. RUNNABLE → TERMINATED

    • 线程执行完 run() 方法或因未捕获的异常终止时,进入终止状态
    • 示例:
      Thread t = new Thread(() -> {System.out.println("Thread running");// run方法执行完毕,线程进入TERMINATED状态
      });
      t.start();
      

1.3 线程状态相关注意事项

  1. 避免线程状态误判

    • Thread.isAlive() 方法返回 true 表示线程处于 RUNNABLE、BLOCKED、WAITING 或 TIMED_WAITING 状态
    • 返回 false 表示处于 NEW 或 TERMINATED 状态
    • 不能仅通过该方法判断线程是否在运行中
  2. sleep() 与 wait() 的区别

    • Thread.sleep(long) 会使线程进入 TIMED_WAITING 状态,但不会释放同步锁
    • Object.wait() 会释放同步锁,且需要在 synchronized 代码块中调用
    • 示例对比:
      // sleep() 示例
      synchronized(lock) {Thread.sleep(1000);  // 持有锁进入休眠
      }// wait() 示例
      synchronized(lock) {lock.wait();  // 释放锁并等待
      }
      

  3. 中断对线程状态的影响

    • 调用 Thread.interrupt() 方法会设置线程的中断标志,但不会直接终止线程
    • 如果线程处于 WAITING 或 TIMED_WAITING 状态,会抛出 InterruptedException 并清除中断标志
    • 正确处理中断的示例:
      try {Thread.sleep(1000);
      } catch (InterruptedException e) {// 恢复中断状态Thread.currentThread().interrupt();// 处理中断逻辑
      }
      

  4. 线程状态监控

    • 可以通过 Thread.getState() 方法获取线程当前状态
    • 调试工具(如JVisualVM)可以显示线程状态图
    • 生产环境监控时,线程状态分析是诊断性能问题的重要手段
  5. 线程池中的线程状态

    • 线程池中的工作线程可能会在 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 {// 处理未销毁的情况
}

http://www.dtcms.com/a/339418.html

相关文章:

  • 水闸安全综合监测系统解决方案
  • Kafka 面试题及详细答案100道(1-10)-- 基础概念与架构
  • NestJS @Inject 装饰器入门教程
  • Hugging Face 核心组件介绍
  • 大功率变速箱总成双联试验台架系统参数
  • 机器人控制基础:运动控制中的串级pid原理以及实现方案(包含代码示例)
  • C/C++ 常见笔试题与陷阱详解
  • .net core web程序如何设置redis预热?
  • 【大白话解析】 OpenZeppelin 的 Address 库:Solidity安全地址交互工具箱​(附源代码)
  • Mybatis执行SQL流程(四)之MyBatis中JDK动态代理
  • Ansible 异步任务管理与内容重用详解
  • 10.Ansible角色管理
  • Ubuntu 和麒麟系统创建新用户 webapp、配置密码、赋予 sudo 权限并禁用 root 的 SSH 登录的详细
  • 网络间的通用语言TCP/IP-网络中的通用规则3
  • 缓存雪崩、缓存穿透、缓存击穿在实际中如何处理
  • Windows Git安装配置
  • PCL+Spigot服务器+python进行MC编程(使用Trae进行AI编程)---可以生成彩虹
  • 代码随想录Day56:图论(冗余连接、冗余连接II)
  • 【python】列表复制注意事项
  • 大模型+RPA:如何用AI实现企业流程自动化的“降本增效”?
  • 什么类型的项目会优先选择Headless CMS
  • 【habitat学习二】Habitat-Lab 快速入门指南(Quickstart)详解
  • 完美解决git报错拉取不到项目
  • 如何禁用 Windows 服务器的自动更新以避免意外重启
  • VMWare主机和客户机无法ping通
  • Android-ContentProvider的跨应用通信学习总结
  • Matplotlib数据可视化实战:Matplotlib安装与入门-跨平台环境配置与基本操作
  • 第四章:大模型(LLM)】07.Prompt工程-(2)Zero-shot Prompt
  • 【Linux】信号(二):Linux原生线程库相关接口
  • C#多线程学习—主子线程,Invoke与begininvoke