简单了解下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改进策略【SPPF】| SimSPPF,简化空间金字塔池化设计,提高计算效率
RT-DETR改进策略【SPPF】| SimSPPF,简化空间金字塔池化设计,提高计算效率
31 19
RT-DETR改进策略【SPPF】| SimSPPF,简化空间金字塔池化设计,提高计算效率
|
8天前
|
XML Java Maven
Spring 手动实现Spring底层机制
Spring 第六节 手动实现Spring底层机制 万字详解!
62 31
|
2天前
|
数据可视化 关系型数据库 MySQL
嵌入式C++、STM32、MySQL、GPS、InfluxDB和MQTT协议数据可视化
通过本文的介绍,我们详细讲解了如何结合嵌入式C++、STM32、MySQL、GPS、InfluxDB和MQTT协议,实现数据的采集、传输、存储和可视化。这种架构在物联网项目中非常常见,可以有效地处理和展示实时数据。希望本文能帮助您更好地理解和应用这些技术,构建高效、可靠的数据处理和可视化系统。
106 82
|
7天前
|
机器学习/深度学习 数据可视化 算法
PyTorch生态系统中的连续深度学习:使用Torchdyn实现连续时间神经网络
神经常微分方程(Neural ODEs)是深度学习领域的创新模型,将神经网络的离散变换扩展为连续时间动力系统。本文基于Torchdyn库介绍Neural ODE的实现与训练方法,涵盖数据集构建、模型构建、基于PyTorch Lightning的训练及实验结果可视化等内容。Torchdyn支持多种数值求解算法和高级特性,适用于生成模型、时间序列分析等领域。
134 77
PyTorch生态系统中的连续深度学习:使用Torchdyn实现连续时间神经网络
|
3天前
|
机器学习/深度学习 人工智能
NeurIPS 2024:收敛速度最高8倍,准确率提升超30%!华科发布MoE Jetpack框架
在NeurIPS 2024会议上,华中科技大学团队发布了MoE Jetpack框架,旨在解决专家混合(MoE)模型训练中的挑战。该框架通过检查点回收和超球面自适应MoE(SpheroMoE)层两项技术,利用预训练密集模型加速收敛并提高准确性。实验表明,MoE Jetpack在视觉任务上显著提升收敛速度(最高8倍)和准确性(超过30%),为MoE模型的实际应用提供了新动力。尽管存在一些限制,如初始权重依赖密集模型及计算资源需求,但该框架大幅降低了MoE模型的训练成本,提升了其可行性。论文地址:https://arxiv.org/abs/2406.04801。
59 45
|
8天前
|
存储 机器学习/深度学习 人工智能
C 408—《数据结构》易错考点200题(含解析)
408考研——《数据结构》精选易错考点200题(含解析)。
72 27
|
4天前
|
监控 UED
产品经理-设计生命周期 - AxureMost
设计生命周期涵盖从概念构思到产品退役的全过程,分为概念与规划、设计与开发、测试与验证、市场推出、维护与优化及衰退与退役六个阶段。每个阶段有特定目标和挑战,确保产品始终围绕用户需求和市场动态调整,保持竞争力。设计团队需灵活应对各阶段任务,以实现产品的成功。
|
8天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
66 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
7天前
|
机器学习/深度学习 算法 Serverless
《当朴素贝叶斯遇上核函数:一场创新的技术融合》
朴素贝叶斯算法基于贝叶斯定理和特征条件独立假设,广泛应用于文本分类、垃圾邮件过滤等场景。核函数通过将数据映射到高维空间解决线性不可分问题,在支持向量机中表现出色。结合两者,利用核函数挖掘非线性关系,可提升朴素贝叶斯对复杂数据的处理能力。然而,这带来了计算复杂性和参数选择的挑战,需采用近似计算和交叉验证等方法应对。这种结合为改进朴素贝叶斯提供了新方向,未来有望在更多领域广泛应用。
51 26
|
8天前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
60 29

热门文章

最新文章