16.7 线程组和未处理的异常
16.7 线程组和未处理的异常
线程组
Java
使用ThreadGroup
来表示线程组,它可以对一批线程进行分类管理,Java
允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程。
用户创建的所有线程都属于指定线程组,如果程序没有显式指定线程属于哪个线程组,则该线程属于默认线程组。
子线程默认和父线程同组
在默认情况下,子线程和创建它的父线程处于同一个线程组内,例如A线程创建了B线程,并且没有指定B线程的线程组,则B线程属于A线程所在的线程组。
一天是不良人一辈子都是不良人
一旦某个线程加入了指定线程组之后,该线程将一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组。
Thread类线程组构造器
Thread
类提供了如下几个构造器来设置新创建的线程属于哪个线程组。
方法 | 描述 |
---|---|
Thread(ThreadGroup group, Runnable target) |
以target 的run() 方法作为线程执行体创建新线程,该线程属于group 线程组。 |
Thread(ThreadGroup group, Runnable target, String name) |
以target 的run() 方法作为线程执行体创建新线程,该线程属于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) |
以指定的名字、指定的父线程组创建一个新线程组。 |
上面两个构造器在创建线程组实例时都必须为其指定一个名字,也就是说,线程组总会具有一个字符串类型的名字,该名字可通过调用ThreadGroup
的getName()
方法来获取,但不允许改变线程组的名字。
ThreadGroup方法
ThreadGroup
类提供了如下几个常用的方法来操作整个线程组里的所有线程。
方法 | 描述 |
---|---|
int activeCount() |
返回此线程组中活动线程的数目 |
void interrupt() |
中断此线程组中的所有线程 |
boolean isDaemon() |
判断该线程组是否是后台线程组 |
void setDaemon(boolean daemon) |
把该线程组设置成后台线程组。 后台线程组具有一个特征: 当后台线程组的最后一个线程执行结束或最后一个线程被销毁后,后台线程组将自动销毁。 |
void setMaxPriority(int pri) |
设置线程组的最高优先级 |
程序 线程组
下面程序创建了几个线程,它们分别属于不同的线程组,程序还将一个线程组设置成后台线程组。
1 |
|
运行效果:
1 | 主线程组的名字:main |
线程组异常处理
处理线程组内抛出的异常
ThreadGroup
内还定义了一个很有用的uncaughtException
方法:
方法 | 描述 |
---|---|
void uncaughtException(Thread t, Throwable e) |
该方法可以处理该线程组内的任意线程所抛出的未处理异常 |
从Java5
开始,Java
加强了线程的异常处理,如果线程执行过程中抛出了一个未处理异常,JVM
在结束该线程之前会自动查找是否有对应的Thread.UncaughtExceptionHandler
对象,如果找到该处理器对象,则会调用该对象的uncaughtException(Thread t, Throwable e)
方法来处理该异常。
Thread.UncaughtExceptionHandler
是Thread
类的一个静态内部接口,该接口内只有一个方法: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 | // 定义自己的异常处理器 |
上面程序的主方法中为主线程设置了异常处理器,而①号代码处将引发一个未处理异常,则该异常处理器会负责处理该异常。运行该程序,会看到如下输出:
1 | Thread[main,5,main] 线程出现了异常:java.lang.ArithmeticException: / by zero |
异常处理器与通过catch
捕获异常的不同
从上面程序的执行结果来看,虽然程序中粗体字代码指定了异常处理器对未捕获的异常进行处理,而且该异常处理器也确实起作用了,但程序依然不会正常结束。这说明异常处理器与通过catch
捕获异常是不同的:
- 当使用
catch
捕获异常时,异常不会向上传播给上一级调用者; - 但使用异常处理器对异常进行处理之后,异常依然会传播给上一级调用者