你好,这里是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.
需要注意的是,这种自动添加和移除线程的行为是默认的,并且不能显式地控制。如果需要更精细的线程管理,可以使用更高级的并发框架,如 ExecutorService
或 ThreadPoolExecutor
。
获取线程组信息
可以使用 ThreadGroup
类的一些方法来获取线程组的信息。以下是一些常用的方法:
getName()
: 获取线程组的名称。getParent()
: 获取父线程组。activeCount()
: 获取当前活跃线程数。activeGroupCount()
: 获取当前活跃子线程组数。enumerate(Thread[] threads)
: 将线程组及其子组中的所有活跃线程复制到指定的线程数组中。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
异常并退出线程。
然后调用了 ThreadGroup
的 interrupt()
方法来中断线程组中的所有线程。这会向两个线程发送中断信号,使它们停止正在执行的任务并抛出 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的探索与实践,持续迭代中。
欢迎评论私信交流。