Java并发编程:什么是线程组?它有什么作用?

线程组简介

在 Java 中,ThreadGroup用于表示一个线程组。我们可以使用ThreadGroup来批量控制线程,更方便地管理线程。

ThreadGroupThread之间的关系非常简单,就像它们的字面意思一样:每个Thread必然存在于一个ThreadGroup中,一个Thread不能独立于ThreadGroup存在。

执行main()方法的线程名字是main,如果你在执行new Thread()时没有显式指定一个ThreadGroup,那么默认会将父线程(当前正在执行new Thread()的线程)的ThreadGroup设置为子线程的ThreadGroup

示例代码:

java 复制代码
public class Demo {
    public static void main(String[] args) {
        Thread subThread = new Thread(() -> {
            System.out.println("子线程所在的线程组名称是:" + 
            Thread.currentThread().getThreadGroup().getName());
            System.out.println("当前线程(子线程)的名称是:" + 
            Thread.currentThread().getName());
        });
        subThread.start();
        System.out.println("执行 main() 方法的线程所在的线程组名称是:"
        + Thread.currentThread().getThreadGroup().getName());
        System.out.println("当前线程的名称是:"
        + Thread.currentThread().getName());
    }
}

输出:

less 复制代码
执行main()方法的线程所在的线程组名称是: main
当前线程的名称是: main
子线程所在的线程组名称是: main
当前线程(子线程)的名称是: Thread-0

线程组是父子结构的,一个线程组可以包含其他线程组,也可以有其他子线程组。从结构上看,线程组是一个树形结构,每个线程属于一个线程组,而该线程组又有一个父线程组,依此类推,最终可以追溯到根线程组,即System线程组。

结构如下所示:

  1. JVM 创建的system线程组是一组用于处理 JVM 系统任务的线程,比如对象销毁、垃圾回收(GC)等。

  2. system线程组的直接子线程组是main线程组,它至少包含一个执行main方法的main线程。

  3. main线程组的子线程组是由应用程序创建的线程组。

你可以在main方法中看到 JVM 创建的system线程组和main线程组:

java 复制代码
public static void main(String[] args) {
    ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
    ThreadGroup systemThreadGroup = mainThreadGroup.getParent();
    System.out.println("当前线程所在的线程组的父线程组名称 = " + systemThreadGroup.getName());
    System.out.println("当前线程所在的线程组名称 = " + mainThreadGroup.getName());
}

输出:

css 复制代码
当前线程所在的线程组的父线程组名称 = system
当前线程所在的线程组名称 = main

一个线程可以访问它所属线程组的信息,但不能访问它所属线程组的父线程组或其他线程组的信息。

线程组的结构

首先,我们来看一下ThreadGroup源码中的成员变量。

java 复制代码
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    private final ThreadGroup parent; // 父线程组
    String name;
    int maxPriority;
    boolean destroyed;
    boolean daemon;
    boolean vmAllowSuspension;
    int nUnstartedThreads = 0;
    int nthreads; // 子线程数量
    Thread threads[]; // 子线程数组
    int ngroups; // 子线程组数量
    ThreadGroup groups[]; // 子线程组数组
}

接下来,我们看一下java.lang.ThreadGroup提供的两个构造函数,我添加了一些注释以便理解。

java 复制代码
// 当 JVM 启动时,调用此构造函数创建根线程组。
private ThreadGroup() {
    this.name = "system";
    this.maxPriority = Thread.MAX_PRIORITY;
    this.parent = null;
}

// 默认情况下,传入当前 ThreadGroup 作为父 ThreadGroup。新线程组的父线程组是当前运行线程的线程组。
public ThreadGroup(String name) {
    this(Thread.currentThread().getThreadGroup(), name);
}

// 传入名称创建线程组,父线程组由客户端指定。
public ThreadGroup(ThreadGroup parent, String name) {
    this(checkParentAccess(parent), parent, name);
}

// 主要的私有构造函数,大多数参数从父线程组继承
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
    this.name = name;
    this.maxPriority = parent.maxPriority;
    this.daemon = parent.daemon;
    this.vmAllowSuspension = parent.vmAllowSuspension;
    this.parent = parent;
    parent.add(this);
}

checkParentAccess()方法用于判断当前运行的线程是否有权限修改线程组。

以下代码演示了这两个构造函数的用法:

java 复制代码
public class ConstructDemo {
    public static void main(String[] args) {
        ThreadGroup subThreadGroup1 = new ThreadGroup("subThreadGroup1");
        ThreadGroup subThreadGroup2 = new ThreadGroup(subThreadGroup1, "subThreadGroup2");
        System.out.println("subThreadGroup1 的父线程组名称是:" +
        subThreadGroup1.getParent().getName());
        System.out.println("subThreadGroup2 的父线程组名称是:" +
        subThreadGroup2.getParent().getName());
    }
}

输出:

makefile 复制代码
subThreadGroup1的父线程组名称是: main
subThreadGroup2的父线程组名称是: subThreadGroup1

ThreadGroup 包含的方法

ThreadGroup提供了许多有用的方法,下面简要介绍其中一些。

方法 描述
void checkAccess() 判断当前运行的线程是否有权限修改线程组。
int activeCount() 返回线程组及其子组中活动线程的估计数量。
int activeGroupCount() 返回线程组及其子组中活动线程组的估计数量。
void destroy() 销毁线程组及其所有子组。
int enumerate(Thread[] list) 将线程组及其子组中的所有活动线程复制到指定的数组中。
int getMaxPriority() 返回线程组的最大优先级。
String getName() 返回线程组的名称。
ThreadGroup getParent() 返回线程组的父线程组。
void interrupt() 中断线程组中的所有线程。
boolean isDaemon() 判断线程组是否是守护线程组。
void setDaemon(boolean daemon) 设置线程组的守护状态。
boolean isDestroyed() 判断线程组是否已被销毁。
void list() 将线程组的信息打印到标准输出。
boolean parentOf(ThreadGroup g) 判断线程组是否是参数线程组或其祖先线程组。
void suspend() 挂起线程组中的所有线程。
void resume() 恢复线程组中所有被挂起的线程。
void setMaxPriority(int prt) 设置线程组的最大优先级。
void stop() 停止线程组中的所有线程。
String toString() 返回线程组的字符串表示。

我们选择其中一些方法来演示用法。

java 复制代码
public class ThreadGroupMethodCase {
    public static void main(String[] args) throws InterruptedException {
        ThreadGroup subgroup1 = new ThreadGroup("subgroup1");
        Thread t1 = new Thread(subgroup1, "t1 in subgroup1");
        Thread t2 = new Thread(subgroup1, "t2 in subgroup1");
        Thread t3 = new Thread(subgroup1, "t3 in subgroup1");
        t1.start();
        Thread.sleep(50);
        t2.start();
        int activeThreadCount = subgroup1.activeCount();
        System.out.println("线程组 " + subgroup1.getName() + " 中的活动线程数量:" + activeThreadCount);
        ThreadGroup subgroup2 = new ThreadGroup("subgroup2");
        Thread t4 = new Thread(subgroup2, "t4 in subgroup2");
        ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
        int activeGroupCount = currentThreadGroup.activeGroupCount();
        System.out.println("线程组 " + currentThreadGroup.getName() + " 中的活动线程组数量:" + activeGroupCount);
        System.out.println("将当前线程组的信息打印到标准输出:");
        currentThreadGroup.list();
    }
}

输出:

less 复制代码
线程组 subgroup1 中的活动线程数量: 2
线程组 main 中的活动线程组数量: 2
将当前线程组的信息打印到标准输出:
java.lang.ThreadGroup[name=main,maxpri=10]
    Thread[main,5,main]
    java.lang.ThreadGroup[name=subgroup1,maxpri=10]
    java.lang.ThreadGroup[name=subgroup2,maxpri=10]

这里有一个有趣的地方:当输出当前线程组中的活动线程数量时,实际上并没有计算状态为NEWTERMINATED的线程。所以当输出subgroup1.activeCount()时,实际上只有一个活动线程,即t2,因为t1已经结束,而t3还没有启动。

总结

简单来说,线程组是一个树形结构,每个线程组下可以有多个线程或多个线程组。线程组可以用于统一控制线程的优先级、检查线程的权限等。

相关推荐
葡萄城技术团队1 分钟前
450 + 公式计算支持,GcExcel Java 强大计算引擎揭秘
java
lifallen8 分钟前
Java BitSet类解析:高效位向量实现
java·开发语言·后端·算法
用户0595661192091 小时前
java 最新技术实操内容:从基础到进阶的全方位指南
java·架构·编程语言
MyFreeIT1 小时前
Unable to start embedded Tomcat
java·tomcat·mybatis
风一样的树懒1 小时前
Zuul动态路由黑洞揭秘:每秒10万并发的刷新策略
java
一只帆記1 小时前
Java 实现后端调用 Chromium 浏览器无头模式截图的方案
java·开发语言
知月玄1 小时前
网页后端开发(基础2--maven单元测试)
java·开发语言
子恒20051 小时前
警惕GO的重复初始化
开发语言·后端·云原生·golang
daiyunchao1 小时前
如何理解"LLM并不理解用户的需求,只是下一个Token的预测,但他能很好的完成任务,比如写对你想要的代码"
后端·ai编程
用户0595661192091 小时前
Java 17 + 特性与现代开发技术实操应用详解
java·机器学习·代码规范