编发编程(二)-ThreadGroup 源码分析
迪丽瓦拉
2025-05-30 16:52:36
0

一、什么是线程组


线程组(ThreadGroup)就是由线程组成的管理线程的类,这个类是java.lang.ThreadGroup类。

在Java中每一个线程都归属于某个线程组管理的一员,例如在主函数main()主工作流程中产生一个线程,则产生的线程属于main这个线程组管理的一员。

A thread group represents a set of threads. In addition, a thread group can also include other thread groups. The thread groups form a tree in which every thread group except the initial thread group has a parent.
A thread is allowed to access information about its own thread group, but not to access information about its thread group's parent thread group or any other thread groups.

线程组表示一组线程。此外,线程组还可以包括其他线程组。线程组形成一个树,其中除了初始线程组之外的每个线程组都有一个父线程。

允许线程访问有关其自身线程组的信息,但不能访问有关其线程组的父线程组或任何其他线程组的相关信息。

二、源码分析


2.1 ThreadGroup Diagrams

2.2 属性


// 父线程组
private final ThreadGroup parent;
// 线程组名称
String name;
// 线程组最大优先级
int maxPriority;
// 销毁状态
boolean destroyed;
// 守护线程组表示: true-守护线程组;false-普通线程组
boolean daemon;
// 虚拟机自动挂起【属性是一个标识符,用于表示当前线程组中的所有线程是否可以被挂起。如果 vmAllowSuspension 属性为 true,则当前线程组中的所有线程可以被挂起;如果 vmAllowSuspension 属性为 false,则当前线程组中的所有线程不可以被挂起。默认情况下,vmAllowSuspension 属性是 true,即所有线程可以被挂起】
boolean vmAllowSuspension;// 未启动线程数(EW状态的线程数)
int nUnstartedThreads = 0;
// 线程总数【仅统计当前线程组中的活动线程数量,不包括子线程组中的线程数量】
int nthreads;
// 线程数组【表示当前线程组中的所有线程,它是一个 Thread 类型的数组,包含了当前线程组中正在运行或等待运行的所有线程】
Thread threads[];
// 线程组数量,【仅统计当前线程组的子线程组数量,不包括子线程组的子线程组数量】
int ngroups;
// 线程组数组
ThreadGroup groups[];

2.3 构造函数

2.4 方法

2.4.1 destory()方法:销毁当前线程组及其所有子线程组和线程。

ThreadGroup 中的 destroy() 方法用于销毁当前线程组及其所有子线程组和线程。方法实现过程如下:

  1. 首先获取当前线程组的子线程组和子线程的快照,以便在销毁子线程组时使用。

  1. 检查当前线程组是否已经被销毁或者线程组中还有正在运行的线程,如果是则抛出 IllegalThreadStateException 异常

  1. 如果当前线程组有子线程组,则递归调用子线程组的 destroy() 方法进行销毁。

  1. 如果当前线程组有父线程组,则从父线程组中移除当前线程组。

  1. 销毁当前线程组,将 destroyed 属性设置为 true,并将线程组中的线程数和子线程组数都设置为 0,将线程组和线程数组都设置为 null。

总之,destroy() 方法用于安全地销毁当前线程组及其所有子线程组和线程,可以避免因线程中的资源没有正确释放而引发一些未知的问 题。但是,需要注意的是,destroy() 方法是一个不安全的方法,需要谨慎使用。


//  销毁此线程组及其所有子组。此线程组必须为空,表示从此线程组中的所有线程都已停止。
public final void destroy() {// 当前线程组下的子线程组数量的快照int ngroupsSnapshot;// 当前线程组下的子线程组的快照ThreadGroup[] groupsSnapshot;synchronized (this) {// 检查当前线程是否可以修改当前线程组及其子线程组的信息checkAccess();// 如果当前线程组已经被销毁(destroyed=true),或者当前线程组中还有正在运行的线程(nthreads>0)if (destroyed || (nthreads > 0)) {throw new IllegalThreadStateException();}ngroupsSnapshot = ngroups;// 如果当前线程组有子线程组,则将子线程组数组复制到一个新的数组中,以便在销毁子线程组时使用if (groups != null) {groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);} else {groupsSnapshot = null;}// 如果当前线程组有父线程组,则将当前线程组从父线程组中移除,并将当前线程组中的线程数、线程组数、线程数组和线程组数组都设置为 null。if (parent != null) {destroyed = true;ngroups = 0;groups = null;nthreads = 0;threads = null;}}// 如果当前线程组有子线程组,则递归调用子线程组的 destroy() 方法进行销毁。for (int i = 0 ; i < ngroupsSnapshot ; i += 1) {groupsSnapshot[i].destroy();}// 如果当前线程组有父线程组,则从父线程组中移除当前线程组。if (parent != null) {parent.remove(this);}
}

2.4.2 add(ThreadGroup g) 方法:

add(ThreadGroup g) 方法,用于向当前线程组添加一个子线程组。

  1. 代码首先使用 synchronized 关键字对当前线程组进行同步,以确保线程安全。

  1. 判断当前线程组是否被销毁,如果已经被销毁,则抛出 IllegalThreadStateException 异常。

  1. 判断当前线程组中是否已经存在子线程组数组 groups,如果不存在,则创建一个长度为 4 的新数组。

  1. 如果数组已经存在但是已经满了,则使用 Arrays.copyOf() 方法将数组扩容为原来的两倍。

  1. 然后将子线程组 g 添加到 groups 数组中,并将 ngroups 属性加一。


// 将指定线程组添加到该线程组中
private final void add(ThreadGroup g){synchronized (this) {// 如果线程组销毁了,就抛出异常if (destroyed) {throw new IllegalThreadStateException();}// 如果线程组为null,则创建 默认大小为4的线程组数组(此处体现出懒加载线程组)if (groups == null) {groups = new ThreadGroup[4];} else if (ngroups == groups.length) {// 如果线程组数组满了,则扩容2倍groups = Arrays.copyOf(groups, ngroups * 2);}// 将指定线程组加入groups[ngroups] = g;// 记录线程组数量加1ngroups++;}
}

2.4.3 add(Thread t) 方法

流程图同2.4.2

add(Thread t) 方法,用于向当前线程组添加一个线程。

  1. 使用 synchronized 关键字对当前线程组进行同步,以确保线程安全。

  1. 判断当前线程组是否已被销毁,如果已经被销毁,则抛出 IllegalThreadStateException 异常。

  1. 判断当前线程组中是否已经存在线程数组 threads,如果不存在,则创建一个长度为 4 的新数组。

  1. 如果数组已经存在但是已经满了,则使用 Arrays.copyOf() 方法将数组扩容为原来的两倍。

  1. 将线程 t 添加到 threads 数组中,

  1. 将 nthreads 属性加一。

  1. 将 nUnstartedThreads 属性减一,表示该线程已经成为线程组的一个成员,即使它可能还没有开始执行。这个操作可以防止线程组被销毁,同时也减少了未启动线程的计数。

void add(Thread t) {synchronized (this) {// 判断当前线程组是否已被销毁,如果已经被销毁,则抛出 IllegalThreadStateException 异常if (destroyed) {throw new IllegalThreadStateException();}// 判断当前线程组中是否已经存在线程数组 threads,如果不存在,则创建一个长度为 4 的新数组if (threads == null) {threads = new Thread[4];} else if (nthreads == threads.length) {// 如果数组已经存在但是已经满了,则使用 Arrays.copyOf() 方法将数组扩容为原来的两倍threads = Arrays.copyOf(threads, nthreads * 2);}// 将线程 t 添加到 threads 数组中threads[nthreads] = t;// 将 nthreads 属性加一nthreads++;// 将 nUnstartedThreads 属性减一,表示该线程已经成为线程组的一个成员,即使它可能还没有开始执行。这个操作可以防止线程组被销毁,同时也减少了未启动线程的计数nUnstartedThreads--;}
}

2.4.4 interrupt()方法

interrupt() 方法,用于中断当前线程组中的所有线程和子线程组中的所有线程。

  1. 使用 synchronized 关键字对当前线程组进行同步,以确保线程安全,

  1. 检查当前线程组的访问权限。

  1. 使用 for 循环遍历当前线程组中的所有线程,并对每个线程调用 interrupt() 方法,以中断该线程。

  1. ngroupsSnapshot 和 groupsSnapshot 两个变量分别记录当前线程组的子线程组数量和子线程组数组。这是因为在遍历子线程组期间,当前线程组的子线程组数组可能会发生修改,因此需要快照来确保遍历期间的线程组数组不发生变化。

  1. for 循环遍历子线程组数组,对每个子线程组中的所有线程调用 interrupt() 方法,以中断该线程。这样,当前线程组中的所有线程和子线程组中的所有线程都被中断了。


public final void interrupt() {// 当前线程组下的子线程组数量的快照int ngroupsSnapshot;// 当前线程组下的子线程组的快照ThreadGroup[] groupsSnapshot;synchronized (this) {// 检查当前线程组的访问权限。checkAccess();// 使用 for 循环遍历当前线程组中的所有线程,并对每个线程调用 interrupt() 方法,以中断该线程。for (int i = 0 ; i < nthreads ; i++) {threads[i].interrupt();}// ngroupsSnapshot 和 groupsSnapshot 两个变量分别记录当前线程组的子线程组数量和子线程组数组ngroupsSnapshot = ngroups;if (groups != null) {groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);} else {groupsSnapshot = null;}}//  for 循环遍历子线程组数组,对每个子线程组中的所有线程调用 interrupt() 方法,for (int i = 0 ; i < ngroupsSnapshot ; i++) {groupsSnapshot[i]

2.4.5 ThreadTerminated(Thread t)

threadTerminated(Thread t) 方法,用于通知当前线程组一个线程已经终止。

  1. 使用 synchronized 关键字对当前线程组进行同步,以确保线程安全。

  1. 调用 remove(Thread t) 方法将线程 t 从当前线程组中删除。

  1. 如果当前线程组中没有线程了(即 nthreads 等于 0),则调用 notifyAll() 方法唤醒等待当前线程组的任何线程。这是因为在当前线程组中没有任何线程时,等待该线程组的线程不需要继续等待,可以继续执行。

  1. 如果当前线程组是守护线程组(即 daemon 为 true)、当前线程组中没有任何线程(即 nthreads 等于 0)、当前线程组中没有未启动的线程(即 nUnstartedThreads 等于 0)以及当前线程组中没有任何子线程组(即 ngroups 等于 0),则调用 destroy() 方法销毁当前线程组。这是因为在这种情况下,当前线程组已经没有任何用处,可以被销毁以释放资源。


// 通知当前线程组一个线程已经终止
void threadTerminated(Thread t) {synchronized (this) {// 将线程 t 从当前线程组中删除remove(t);// 当前线程组中没有线程了if (nthreads == 0) {// 唤醒等待当前线程组的任何线程notifyAll();}// 当前线程组是守护线程组、线程组中没有任何线程、线程组中没有未启动的线程、当前线程组中没有任何子线程组if (daemon && (nthreads == 0) &&(nUnstartedThreads == 0) && (ngroups == 0)){// 销毁当前线程组destroy();}}
}

2.4.6 remove(ThreadGroup g)方法

remove(ThreadGroup g)方法, 用于从ThreadGroup中移除给定的ThreadGroup对象g。

  1. 检查ThreadGroup对象是否已经被销毁,如果已经销毁,则直接返回。

  1. 遍历ThreadGroup中的所有子线程组,找到指定的ThreadGroup对象g,并将其从ThreadGroup中移除。

  1. 如果ThreadGroup中没有任何线程,则唤醒所有等待的线程。

  1. 如果ThreadGroup是一个守护线程组,并且没有任何未开始的线程、没有任何子线程组和没有任何线程,则销毁该ThreadGroup。


// 用于从ThreadGroup中移除给定的ThreadGroup对象g。
private void remove(ThreadGroup g) {synchronized (this) {// 如果已经销毁,则直接返回if (destroyed) {return;}// 遍历ThreadGroup中的所有子线程组,找到指定的ThreadGroup对象g,并将其从ThreadGroup中移除for (int i = 0 ; i < ngroups ; i++) {if (groups[i] == g) {ngroups -= 1;System.arraycopy(groups, i + 1, groups, i, ngroups - i);groups[ngroups] = null;break;}}// 如果ThreadGroup中没有任何线程,则唤醒所有等待的线程。if (nthreads == 0) {notifyAll();}// 如果ThreadGroup是一个守护线程组,并且没有任何未开始的线程、没有任何子线程组和没有任何线程,则销毁该ThreadGroup。if (daemon && (nthreads == 0) &&(nUnstartedThreads == 0) && (ngroups == 0)){// 销毁该ThreadGroupdestroy();}}
}

2.4.7 activeGroupCount() 方法

activeGroupCount()方法,用于获取ThreadGroup中当前活动的线程组数。

  1. 在同步块中获取ThreadGroup中的ngroups和groups数组的快照,以避免在方法执行期间对ThreadGroup的修改影响结果;

  1. 如果线程组已经销毁,则返回0;

  1. 遍历ThreadGroup中的所有子线程组,对每个子线程组递归调用其activeGroupCount方法,将结果累加到一个计数器中,

  1. 返回计数器的值,即ThreadGroup中当前活动的线程组数。


// 获取ThreadGroup中当前活动的线程组数
public int activeGroupCount() {// 当前线程组下的子线程组数量的快照int ngroupsSnapshot;// 当前线程组下的子线程组的快照ThreadGroup[] groupsSnapshot;synchronized (this) {// 如果当前线程组已经销毁,返回0if (destroyed) {return 0;}ngroupsSnapshot = ngroups;if (groups != null) {groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);} else {groupsSnapshot = null;}}int n = ngroupsSnapshot;// 遍历ThreadGroup中的所有子线程组,对每个子线程组递归调用其activeGroupCount方法,将结果累加到一个计数器中,for (int i = 0 ; i < ngroupsSnapshot ; i++) {n += groupsSnapshot[i].activeGroupCount();}// 返回计数器的值,即ThreadGroup中当前活动的线程组数。return n;
}

2.4.8 enumerate(ThreadGroup list[], int n, boolean recurse) 方法

enumerate(ThreadGroup list[], int n, boolean recurse) 方法,用于把此线程组中的所有活动子组的引用复制到指定线程数组中。

  1. 在同步块中获取ThreadGroup中的ngroups和groups数组的快照,然后将ThreadGroup中的线程组复制到给定的列表中。

  1. 如果递归标志recurse为true,则它会遍历ThreadGroup中的所有子线程组,并对每个子线程组调用其enumerate方法,将结果累加到列表中。

  1. 返回列表中包含的线程组数。该方法返回的整数值表示在调用方法之前已经向列表中添加的线程组数。如果列表已满,该方法会返回未添加到列表中的线程组数,这些线程组仍然存在于ThreadGroup中。

// 把此线程组中的所有活动子组的引用复制到指定线程数组中。
public int enumerate(ThreadGroup list[], boolean recurse) {checkAccess();return enumerate(list, 0, recurse);
}private int enumerate(ThreadGroup list[], int n, boolean recurse) {// 当前线程组下的子线程组数量的快照int ngroupsSnapshot = 0;// 当前线程组下的子线程组的快照ThreadGroup[] groupsSnapshot = null;synchronized (this) {// 如果销毁,则返回0if (destroyed) {return 0;}// 线程组数量,【仅统计当前线程组的子线程组数量,不包括子线程组的子线程组数量】int ng = ngroups;if (ng > list.length - n) {ng = list.length - n;}if (ng > 0) {System.arraycopy(groups, 0, list, n, ng);n += ng;}// 如果递归标志recurse为true,if (recurse) {ngroupsSnapshot = ngroups;// 线程组数组不为nullif (groups != null) {// 复制到新线程组groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);} else {groupsSnapshot = null;}}}// 如果递归标志recurse为true,则它会遍历ThreadGroup中的所有子线程组,并对每个子线程组调用其enumerate方法,将结果累加到列表中。if (recurse) {for (int i = 0 ; i < ngroupsSnapshot ; i++) {n = groupsSnapshot[i].enumerate(list, n, true);}}return n;
}

相关内容