线程组简介
在 Java 中,ThreadGroup用于表示一个线程组。我们可以使用ThreadGroup来批量控制线程,更方便地管理线程。
ThreadGroup和Thread之间的关系非常简单,就像它们的字面意思一样:每个Thread必然存在于一个ThreadGroup中,一个Thread不能独立于ThreadGroup存在。
执行main()方法的线程名字是main,如果你在执行new Thread()时没有显式指定一个ThreadGroup,那么默认会将父线程(当前正在执行new Thread()的线程)的ThreadGroup设置为子线程的ThreadGroup。
示例代码:
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());
}
}
输出:
执行main()方法的线程所在的线程组名称是: main
当前线程的名称是: main
子线程所在的线程组名称是: main
当前线程(子线程)的名称是: Thread-0
线程组是父子结构的,一个线程组可以包含其他线程组,也可以有其他子线程组。从结构上看,线程组是一个树形结构,每个线程属于一个线程组,而该线程组又有一个父线程组,依此类推,最终可以追溯到根线程组,即System线程组。
结构如下所示:
图片
- JVM 创建的system线程组是一组用于处理 JVM 系统任务的线程,比如对象销毁、垃圾回收(GC)等。
- system线程组的直接子线程组是main线程组,它至少包含一个执行main方法的main线程。
- main线程组的子线程组是由应用程序创建的线程组。
你可以在main方法中看到 JVM 创建的system线程组和main线程组:
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());
}
输出:
当前线程所在的线程组的父线程组名称 = system
当前线程所在的线程组名称 = main
一个线程可以访问它所属线程组的信息,但不能访问它所属线程组的父线程组或其他线程组的信息。
线程组的结构
首先,我们来看一下ThreadGroup源码中的成员变量。
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提供的两个构造函数,我添加了一些注释以便理解。
// 当 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()方法用于判断当前运行的线程是否有权限修改线程组。
以下代码演示了这两个构造函数的用法:
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());
}
}
输出:
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() | 返回线程组的字符串表示。 |
我们选择其中一些方法来演示用法。
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();
}
}
输出:
线程组 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]
这里有一个有趣的地方:当输出当前线程组中的活动线程数量时,实际上并没有计算状态为NEW和TERMINATED的线程。所以当输出subgroup1.activeCount()时,实际上只有一个活动线程,即t2,因为t1已经结束,而t3还没有启动。
总结
简单来说,线程组是一个树形结构,每个线程组下可以有多个线程或多个线程组。线程组可以用于统一控制线程的优先级、检查线程的权限等。