简单了解下ThreadGroup的使用

简介: ThreadGroup 并不是用来管理 Thread 的,而是针对 Thread 的一个组织。ThreadGroup 可以包含一组相关的线程,并且可以对这组线程进行集中管理。

你好,这里是codetrend专栏“高并发编程基础”。

ThreadGroup 并不是用来管理 Thread 的,而是针对 Thread 的一个组织。ThreadGroup 可以包含一组相关的线程,并且可以对这组线程进行集中管理。 ThreadGroup 提供了比较多的API,本文将对其介绍。

在 Java 中,ThreadGroup 是一种用于组织线程的机制。ThreadGroup 可以包含一组相关的线程,并且可以对这组线程进行集中管理。

ThreadGroup 类提供了以下功能:

  • 线程组的创建和销毁:可以通过 ThreadGroup 构造函数创建 ThreadGroup 对象,并使用 destroy() 方法销毁线程组。
  • 添加和移除线程:添加通过Thread构造函数指定ThreadGroup,线程结束后会自动移除。
  • 获取线程组信息:可以使用 getName() 方法获取线程组的名称,使用 getParent() 方法获取父线程组,使用 activeCount() 方法获取活动线程的数量,使用 enumerate(Thread[]) 方法获取线程组中的线程列表等。
  • 处理未捕获异常:可以使用 uncaughtException(Thread, Throwable) 方法在发生未捕获异常时进行处理。
  • 设置线程组的优先级:可以使用 setMaxPriority(int) 方法设置线程组的最大优先级。
  • 调用线程组中所有线程的 interrupt() 方法:可以使用 interrupt() 方法中断线程组中的所有线程。

线程组的创建和销毁

默认情况下,新的线程都会被加入到main线程所在的group中,main线程的group名字同线程名。如同线程存在父子关系一样,ThreadGroup同样也存在父子关系。所有线程都有一个threadGroup。

在 Java 中,可以通过 ThreadGroup 类来创建和销毁线程组。以下是创建和销毁线程组的示例代码:

创建线程组:

// 通过调用 ThreadGroup 的构造函数,可以创建一个名为 "MyThreadGroup" 的线程组。
ThreadGroup group = new ThreadGroup("MyThreadGroup");

销毁线程组:

// 可以使用 ThreadGroup 的 `destroy()` 方法来销毁线程组。销毁线程组会自动停止该线程组内所有的线程,并且无法再添加新的线程。
group.destroy();

需要注意的是,销毁线程组时,该线程组必须没有任何活动线程。如果线程组中还有活动线程,那么调用 destroy() 方法会抛出 IllegalThreadStateException 异常。

下面是一个完整的示例代码,演示了如何创建和销毁线程组:

public class ThreadGroupExample {
   
    public static void main(String[] args) {
   
        ThreadGroup group = new ThreadGroup("MyThreadGroup");

        Thread t1 = new Thread(group, () -> {
   
            System.out.println("Thread 1 is running");
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(group, () -> {
   
            System.out.println("Thread 2 is running");
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        });

        t1.start();
        t2.start();

        // 假设线程执行一段时间后,线程组中没有活动线程了
        group.destroy();
    }
}

需要注意的是,销毁线程组是一种较为激进的操作,通常在确保线程组中没有活动线程时进行,以避免出现异常情况。因此,在实际开发中,更常见的做法是让线程组自然结束,而不显式销毁线程组。从JDK16开始,这个接口已经过时,并且在将来的jdk版本中会被移除。

添加和移除线程

从 JDK 1.5 开始,Java 引入了 ThreadGroup 的垃圾回收机制改进,不再需要显式地添加或移除线程。相反,可以通过创建线程时指定线程组来自动将线程添加到相应的线程组中,并且当线程终止后,它会自动从线程组中移除。

以下是一个示例代码,演示了如何使用线程组将线程自动添加到相应的线程组中:

//  "MyThreadGroup" 的线程组,并将两个线程 (`t1` 和 `t2`) 创建时指定线程组为 `group`。这样,这两个线程就会自动添加到 `group` 线程组中。
package engineer.concurrent.battle.igroup;

public class ThreadGroupAddExample {
   
    public static void main(String[] args) throws InterruptedException {
   
        ThreadGroup group = new ThreadGroup("MyThreadGroup");

        Thread t1 = new Thread(group, () -> {
   
            System.out.println("Thread 1 is running");
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(group, () -> {
   
            System.out.println("Thread 2 is running");
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("All threads are finished");
        // 打印线程列表
        Thread[] tArr = new Thread[2];
        group.enumerate(tArr);
        for (int i = 0; i < tArr.length; i++) {
   
            if(tArr[i]!=null){
   
                System.out.println(tArr[i].getName());
            }else{
   
                System.out.println("thread exit.");
            }
        }
    }
}

输出结果如下:

Thread 1 is running
Thread 2 is running
All threads are finished
thread exit.
thread exit.

需要注意的是,这种自动添加和移除线程的行为是默认的,并且不能显式地控制。如果需要更精细的线程管理,可以使用更高级的并发框架,如 ExecutorServiceThreadPoolExecutor

获取线程组信息

可以使用 ThreadGroup 类的一些方法来获取线程组的信息。以下是一些常用的方法:

  1. getName(): 获取线程组的名称。
  2. getParent(): 获取父线程组。
  3. activeCount(): 获取当前活跃线程数。
  4. activeGroupCount(): 获取当前活跃子线程组数。
  5. enumerate(Thread[] threads): 将线程组及其子组中的所有活跃线程复制到指定的线程数组中。
  6. enumerate(ThreadGroup[] groups): 将线程组及其子组中的所有活跃子线程组复制到指定的线程组数组中。

下面是一个示例代码,演示了如何使用这些方法获取线程组的信息:

package engineer.concurrent.battle.igroup;

public class ThreadGroupInfoDemo {
   
    public static void main(String[] args) {
   
        ThreadGroup group = new ThreadGroup("MyThreadGroup");

        Thread t1 = new Thread(group, () -> {
   
            System.out.println("Thread 1 is running");
            try {
   
                Thread.sleep(3000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(group, () -> {
   
            System.out.println("Thread 2 is running");
            try {
   
                Thread.sleep(3000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        });

        t1.start();
        t2.start();

        // 获取线程组的信息
        System.out.println("线程组名称:" + group.getName());
        System.out.println("父线程组:" + group.getParent());
        System.out.println("活跃线程数:" + group.activeCount());
        System.out.println("活跃子线程组数:" + group.activeGroupCount());

        Thread[] threads = new Thread[group.activeCount()];
        group.enumerate(threads);
        System.out.println("活跃线程列表:");
        for (Thread thread : threads) {
   
            System.out.println(thread);
        }

        ThreadGroup[] subGroups = new ThreadGroup[group.activeGroupCount()];
        group.enumerate(subGroups);
        System.out.println("活跃子线程组列表:");
        for (ThreadGroup subGroup : subGroups) {
   
            System.out.println(subGroup);
        }
    }
}

输出可能类似于以下内容:

线程组名称:MyThreadGroup
父线程组:java.lang.ThreadGroup[name=main,maxpri=10]
活跃线程数:2
活跃子线程组数:0
活跃线程列表:
Thread[Thread-0,5,MyThreadGroup]
Thread[Thread-1,5,MyThreadGroup]
活跃子线程组列表:

处理未捕获异常

可以使用 ThreadGroup 类来处理线程组中未捕获的异常。当一个线程抛出一个未捕获的异常时,如果该线程是属于一个线程组的,那么这个异常就会被传播到其所属线程组中的 uncaughtException(Thread t, Throwable e) 方法中进行处理。

下面是一个示例代码,演示了如何使用 ThreadGroup 处理未捕获的异常:

public class ThreadGroupExceptionDemo {
   
    public static void main(String[] args) {
   
        // 创建一个线程组,并重写 uncaughtException 方法来处理未捕获的异常
        ThreadGroup group = new ThreadGroup("MyThreadGroup") {
   
            @Override
            public void uncaughtException(Thread t, Throwable e) {
   
                System.out.println("Thread " + t.getName() + " throws exception: " + e.getMessage());
            }
        };

        // 在线程组中启动一个线程
        Thread t = new Thread(group, () -> {
   
            throw new RuntimeException("Test Exception");
        });
        t.start();
    }
}

在这个示例中创建了一个名为 "MyThreadGroup" 的线程组,并重写了 uncaughtException 方法来处理未捕获的异常。然后,启动一个线程并在其中抛出一个 RuntimeException 异常。由于这个线程属于线程组 "MyThreadGroup",因此当它抛出异常时,这个异常就会被传播到线程组中的 uncaughtException 方法中进行处理。

输出可能类似于以下内容:

Thread Thread-0 throws exception: Test Exception

设置线程组的优先级

可以使用 ThreadGroup 类的 setMaxPriority(int priority) 方法来设置线程组的优先级。这个方法可以将线程组中所有线程的优先级限制为指定的值,如果线程拥有更高的优先级则不受重新设置优先级。

下面是一个示例代码,演示了如何使用 ThreadGroup 设置线程组的优先级:

public class ThreadGroupPriorityExample {
   
    public static void main(String[] args) {
   
        // 创建一个线程组,并将其优先级设置为 5
        ThreadGroup group = new ThreadGroup("MyThreadGroup");
        group.setMaxPriority(5);

        // 在线程组中启动两个线程
        Thread t1 = new Thread(group, () -> {
   
            System.out.println("Thread 1 priority: " + Thread.currentThread().getPriority());
        });
        Thread t2 = new Thread(group, () -> {
   
            System.out.println("Thread 2 priority: " + Thread.currentThread().getPriority());
        });
        t1.start();
        t2.start();
    }
}

在这个示例中创建了一个名为 "MyThreadGroup" 的线程组,并将其优先级设置为 5。然后启动了两个线程,并在其中分别输出它们的优先级。

由于这两个线程属于同一个线程组,因此它们的优先级都受到了线程组的限制。在输出中可以看到这两个线程的优先级都是 5。

输出可能类似于以下内容:

Thread 1 priority: 5
Thread 2 priority: 5

中断线程组中的所有线程

可以使用 ThreadGroup 类的 interrupt() 方法来中断线程组中的所有线程。这个方法会向线程组中的所有线程发送中断信号,使它们停止正在执行的任务并抛出 InterruptedException 异常。

下面是一个示例代码,演示了如何使用 ThreadGroup 中断线程组中的所有线程:

package engineer.concurrent.battle.igroup;

import java.util.concurrent.TimeUnit;

public class ThreadGroupInterruptExample {
   
    public static void main(String[] args) throws InterruptedException {
   
        // 创建一个线程组,并启动两个线程
        ThreadGroup group = new ThreadGroup("MyThreadGroup");
        Thread t1 = new Thread(group, () -> {
   
            while (!Thread.interrupted()) {
   
                System.out.println("Thread 1 is running...");
                try {
   
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
   
                    // 捕获中断异常并退出线程
                    System.out.println("Thread 1 is interrupted!");
                    return;
                }
            }
        });
        Thread t2 = new Thread(group, () -> {
   
            while (!Thread.interrupted()) {
   
                System.out.println("Thread 2 is running...");
                try {
   
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
   
                    // 捕获中断异常并退出线程
                    System.out.println("Thread 2 is interrupted!");
                    return;
                }
            }
        });
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(3);
        // 中断线程组中的所有线程
        group.interrupt();
    }
}

在这个示例中创建了一个名为 "MyThreadGroup" 的线程组,并启动了两个线程。这两个线程会一直循环执行直到被中断,每隔一秒输出一条信息。在每个线程的执行过程中使用 Thread.interrupted() 方法来判断是否收到中断信号。如果收到了中断信号,就会抛出 InterruptedException 异常并退出线程。

然后调用了 ThreadGroupinterrupt() 方法来中断线程组中的所有线程。这会向两个线程发送中断信号,使它们停止正在执行的任务并抛出 InterruptedException 异常。

输出可能类似于以下内容:

Thread 1 is running...
Thread 2 is running...
Thread 1 is interrupted!
Thread 2 is interrupted!

java.lang.ThreadGroup#interrupt 的源码如下,可以看到synchronized加锁后对当前线程组一个个遍历发起interrupt。

public final void interrupt() {
   
    int ngroupsSnapshot;
    ThreadGroup[] groupsSnapshot;
    synchronized (this) {
   
        checkAccess();
        for (int i = 0 ; i < nthreads ; i++) {
   
            threads[i].interrupt();
        }
        ngroupsSnapshot = ngroups;
        if (groups != null) {
   
            groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
        } else {
   
            groupsSnapshot = null;
        }
    }
    for (int i = 0 ; i < ngroupsSnapshot ; i++) {
   
        groupsSnapshot[i].interrupt();
    }
}

其它接口信息

  • java.lang.ThreadGroup#setDaemon ,设置守护线程的属性,true的话在所有线程执行完成后会调用java.lang.ThreadGroup#destroy方法,这两个接口是过时的。摧毁 ThreadGroup 的 API 和机制本质上存在缺陷。在未来的版本中,将删除显式或自动销毁线程组的功能,以及守护线程组的概念。
  • java.lang.ThreadGroup#resume或suspend 这两个接口已被弃用,因为它天生容易发生死锁。如果目标线程在被暂停时持有保护关键系统资源的监视器锁定,任何线程在目标线程恢复之前都无法访问该资源。如果尝试在调用 resume 之前锁定此监视器的线程来恢复目标线程,则会导致死锁。这样的死锁通常表现为"冻结"的进程。

关于作者

开发|界面|引擎|交付|副驾——重写全栈法则:AI原生的倍速造应用流

来自全栈程序员nine的探索与实践,持续迭代中。

欢迎评论私信交流。

目录
相关文章
|
1月前
|
编解码 异构计算
RT-DETR改进策略【Neck】| BiFPN:双向特征金字塔网络-跨尺度连接和加权特征融合
RT-DETR改进策略【Neck】| BiFPN:双向特征金字塔网络-跨尺度连接和加权特征融合
104 9
RT-DETR改进策略【Neck】| BiFPN:双向特征金字塔网络-跨尺度连接和加权特征融合
|
1月前
|
机器学习/深度学习 人工智能 编解码
Lumina-Image 2.0:上海 AI Lab 开源的统一图像生成模型,支持生成多分辨率、多风格的图像
Lumina-Image 2.0 是上海 AI Lab 开源的高效统一图像生成模型,参数量为26亿,基于扩散模型和Transformer架构,支持多种推理求解器,能生成高质量、多风格的图像。
186 17
Lumina-Image 2.0:上海 AI Lab 开源的统一图像生成模型,支持生成多分辨率、多风格的图像
|
1月前
|
人工智能 计算机视觉
RT-DETR改进策略【损失函数篇】| NWD损失函数,提高小目标检测精度
RT-DETR改进策略【损失函数篇】| NWD损失函数,提高小目标检测精度
100 5
RT-DETR改进策略【损失函数篇】| NWD损失函数,提高小目标检测精度
|
1月前
|
关系型数据库 决策智能
RT-DETR改进策略【损失函数篇】| Slide Loss,解决简单样本和困难样本之间的不平衡问题
RT-DETR改进策略【损失函数篇】| Slide Loss,解决简单样本和困难样本之间的不平衡问题
91 3
RT-DETR改进策略【损失函数篇】| Slide Loss,解决简单样本和困难样本之间的不平衡问题
|
1月前
|
机器学习/深度学习 PyTorch TensorFlow
DGL(0.8.x) 技术点分析
DGL是由Amazon发布的图神经网络开源库,支持TensorFlow、PyTorch和MXNet。DGL采用消息传递范式进行图计算,包括边上计算、消息函数、点上计算、聚合与更新函数等。其架构分为顶层业务抽象、Backend多后端适配、Platform高效计算适配以及C++性能敏感功能层,确保高效、灵活的图神经网络开发。
|
1月前
|
机器学习/深度学习 测试技术 知识图谱
DeepSeek-R1:Incentivizing Reasoning Capability in LLMs via Reinforcement Learning论文解读
DeepSeek团队推出了第一代推理模型DeepSeek-R1-Zero和DeepSeek-R1。DeepSeek-R1-Zero通过大规模强化学习训练,展示了卓越的推理能力,但存在可读性和语言混合问题。为此,团队引入多阶段训练和冷启动数据,推出性能与OpenAI-o1-1217相当的DeepSeek-R1,并开源了多个密集模型。实验表明,DeepSeek-R1在多项任务上表现出色,尤其在编码任务上超越多数模型。未来研究将聚焦提升通用能力和优化提示工程等方向。
186 16
|
1月前
|
机器学习/深度学习 资源调度 计算机视觉
RT-DETR改进入门篇 | 手把手讲解改进模块如何实现高效涨点,以SimAM注意力模块为例
RT-DETR改进入门篇 | 手把手讲解改进模块如何实现高效涨点,以SimAM注意力模块为例
79 2
RT-DETR改进入门篇 | 手把手讲解改进模块如何实现高效涨点,以SimAM注意力模块为例
|
1月前
|
机器学习/深度学习 数据可视化 网络架构
RT-DETR改进策略【SPPF】| NeuralPS-2022 Focal Modulation : 使用焦点调制模块优化空间金字塔池化SPPF
RT-DETR改进策略【SPPF】| NeuralPS-2022 Focal Modulation : 使用焦点调制模块优化空间金字塔池化SPPF
59 18
RT-DETR改进策略【SPPF】| NeuralPS-2022 Focal Modulation : 使用焦点调制模块优化空间金字塔池化SPPF
|
1月前
|
资源调度 自然语言处理 网络架构
RT-DETR改进策略【Neck】| 使用CARAFE轻量级通用上采样算子
RT-DETR改进策略【Neck】| 使用CARAFE轻量级通用上采样算子
48 11
RT-DETR改进策略【Neck】| 使用CARAFE轻量级通用上采样算子
|
1月前
|
机器学习/深度学习 计算机视觉
RT-DETR改进策略【Neck】| 2023 显式视觉中心EVC 优化特征提取金字塔,对密集预测任务非常有效
RT-DETR改进策略【Neck】| 2023 显式视觉中心EVC 优化特征提取金字塔,对密集预测任务非常有效
56 11
RT-DETR改进策略【Neck】| 2023 显式视觉中心EVC 优化特征提取金字塔,对密集预测任务非常有效