16.7 线程组和未处理的异常

16.7 线程组和未处理的异常

线程组

Java使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程
用户创建的所有线程都属于指定线程组,如果程序没有显式指定线程属于哪个线程组,则该线程属于默认线程组

子线程默认和父线程同组

在默认情况下,子线程和创建它的父线程处于同一个线程组内,例如A线程创建了B线程,并且没有指定B线程的线程组,则B线程属于A线程所在的线程组。

一天是不良人一辈子都是不良人

一旦某个线程加入了指定线程组之后,该线程将一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组。

Thread类线程组构造器

Thread类提供了如下几个构造器来设置新创建的线程属于哪个线程组。

方法 描述
Thread(ThreadGroup group, Runnable target) targetrun()方法作为线程执行体创建新线程,该线程属于group线程组。
Thread(ThreadGroup group, Runnable target, String name) targetrun()方法作为线程执行体创建新线程,该线程属于group线程组,且线程名为name
Thread(ThreadGroup group, String name) 创建新线程,新线程名为name,属于group线程组。
Thread(ThreadGroup group, Runnable target, String name, long stackSize) Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group, and has the specified stack size.
Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) Allocates a new Thread object so that it has target as its run object, has the specified name as its name, belongs to the thread group referred to by group, has the specified stackSize, and inherits initial values for inheritable thread-local variables if inheritThreadLocals is true.

获取当前线程的线程组

因为中途不可改变线程所属的线程组,所以Thread类没有提供setThreadGroup()方法来改变线程所属的线程组,但提供了一个getThreadGroup()方法来返回该线程所属的线程组,getThreadGroup()方法的返回值是ThreadGroup对象,表示一个线程组。

方法 描述
ThreadGroup getThreadGroup() Returns the thread group to which this thread belongs.

ThreadGroup构造器

ThreadGroup类提供了如下两个简单的构造器来创建实例。

方法 描述
ThreadGroup(String name) 以指定的线程组名字来创建新的线程组。
ThreadGroup(ThreadGroup parent, String name) 以指定的名字、指定的父线程组创建一个新线程组。

上面两个构造器在创建线程组实例时都必须为其指定一个名字,也就是说,线程组总会具有一个字符串类型的名字,该名字可通过调用ThreadGroupgetName()方法来获取,但不允许改变线程组的名字

ThreadGroup方法

ThreadGroup类提供了如下几个常用的方法来操作整个线程组里的所有线程。

方法 描述
int activeCount() 返回此线程组中活动线程的数目
void interrupt() 中断此线程组中的所有线程
boolean isDaemon() 判断该线程组是否是后台线程组
void setDaemon(boolean daemon) 把该线程组设置成后台线程组。
后台线程组具有一个特征:
当后台线程组的最后一个线程执行结束或最后一个线程被销毁后,后台线程组将自动销毁
void setMaxPriority(int pri) 设置线程组的最高优先级

程序 线程组

下面程序创建了几个线程,它们分别属于不同的线程组,程序还将一个线程组设置成后台线程组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

class MyThread extends Thread {
// 提供指定线程名的构造器
public MyThread(String name) {
super(name);
}

// 提供指定线程名、线程组的构造器
public MyThread(ThreadGroup group, String name) {
super(group, name);
}

public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(getName() + " 线程的i变量" + i);
}
}
}

public class ThreadGroupTest {
public static void main(String[] args) {
// 获取主线程所在的线程组,这是所有线程默认的线程组
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
System.out.println("主线程组的名字:" + mainGroup.getName());
System.out.println("主线程组是否是后台线程组:" + mainGroup.isDaemon());
new MyThread("主线程组的线程").start();
ThreadGroup tg = new ThreadGroup("新线程组");
// 设为后台线程
tg.setDaemon(true);
System.out.println("tg线程组是否是后台线程组:" + tg.isDaemon());
MyThread tt = new MyThread(tg, "tg组的线程甲");
tt.start();
new MyThread(tg, "tg组的线程乙").start();
}
}

运行效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
主线程组的名字:main
主线程组是否是后台线程组:false
tg线程组是否是后台线程组:true
主线程组的线程 线程的i变量0
主线程组的线程 线程的i变量1
tg组的线程甲 线程的i变量0
tg组的线程乙 线程的i变量0
主线程组的线程 线程的i变量2
...
tg组的线程乙 线程的i变量19
tg组的线程甲 线程的i变量10
...
主线程组的线程 线程的i变量19
tg组的线程甲 线程的i变量18
tg组的线程甲 线程的i变量19

线程组异常处理

处理线程组内抛出的异常

ThreadGroup内还定义了一个很有用的uncaughtException方法:

方法 描述
void uncaughtException(Thread t, Throwable e) 该方法可以处理该线程组内的任意线程所抛出的未处理异常

Java5开始,Java加强了线程的异常处理,如果线程执行过程中抛出了一个未处理异常,JVM在结束该线程之前会自动查找是否有对应的Thread.UncaughtExceptionHandler对象,如果找到该处理器对象,则会调用该对象的uncaughtException(Thread t, Throwable e)方法来处理该异常。

Thread.UncaughtExceptionHandlerThread类的一个静态内部接口,该接口内只有一个方法:void uncaughtException(Thread t, Throwable e),该方法中的t代表出现异常的线程,而e代表该线程抛出的异常。

设置线程异常处理器

Thread类提供了如下两个方法来设置异常处理器

方法 描述
static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 为该线程类的所有线程实例设置默认的异常处理器。
set UncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 为指定的线程实例设置异常处理器

ThreadGroup类实现了ThreadUncaughtHandler接口,所以每个线程所属的线程组将会作为默认的异常处理器。当一个线程抛出未处理异常时,

  • 首先,JVM会査找该异常对应的异常处理器(setUncaughtExceptionHandler方法设置的异常处理器),如果找到该异常处理器,则将调用该异常处理器处理该异常;
  • 否则,JVM将会调用该线程所属的线程组对象的uncaughtException方法来处理该异常线程组处理异常的默认流程如下。
    • 如果该线程组有父线程组,则调用父线程组的uncaughtException()方法来处理该异常
    • 如果该线程实例所属的线程类有默认的异常处理器(由setDefaultUncaughtExceptionHandler方法设置的异常处理器),那么就调用该异常处理器来处理该异常。
    • 如果该异常对象是ThreadDeath的对象,则不做任何处理;否则,将异常跟踪栈的信息打印到System.Err错误输出流,并结束该线程。

程序 为线程设置异常处理器

下面程序为主线程设置了异常处理器,当主线程运行抛出未处理异常时,该异常处理器将会起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义自己的异常处理器
class MyExHandler implements Thread.UncaughtExceptionHandler {
// 实现uncaughtException方法,该方法将处理线程的未处理异常
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t + " 线程出现了异常:" + e);
}
}

public class ExHandler {
public static void main(String[] args) {
// 设置主线程的异常处理器
Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());
int a = 5 / 0; // ①
System.out.println("程序正常结束!");
}
}

上面程序的主方法中为主线程设置了异常处理器,而①号代码处将引发一个未处理异常,则该异常处理器会负责处理该异常。运行该程序,会看到如下输出:

1
Thread[main,5,main] 线程出现了异常:java.lang.ArithmeticException: / by zero

异常处理器与通过catch捕获异常的不同

从上面程序的执行结果来看,虽然程序中粗体字代码指定了异常处理器对未捕获的异常进行处理,而且该异常处理器也确实起作用了,但程序依然不会正常结束。这说明异常处理器与通过catch捕获异常是不同的:

  • 当使用catch捕获异常时,异常不会向上传播给上一级调用者;
  • 使用异常处理器对异常进行处理之后,异常依然会传播给上一级调用者