分组线程是Java并发API中一个有趣的功能。此功能将一组线程作为一个独立单元,并且能够对组里线程对象操作提供使用权。例如,如果你想控制一些运行相同任务的线程,就可以用一个单独指令中断组里所有的线程。
Java提供ThreadGroup了来操作一组线程。一个ThreadGroup对象通过线程对象生成,另一个ThreadGroup对象,创建线程的树结构。
在“控制线程中断”小节中,我们学会如何使用一个通用方法处理线程对象抛出的所有非捕获异常。在“处理线程非受控异常”小节中,我们编写一个处理器来解决线程抛出的非捕获异常。我们也可以使用相同的原理来处理线程或线程组抛出的非捕获异常。
本节中,通过范例学习使用ThreadGroup对象,如何实现和配置处理器来解决一组线程抛出的非捕获异常。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
首先,创建名为MyThreadGroup的类,继承ThreadGroup类。因为ThreadGroup类本身没有构造函数,所以在此类中定义具有一个参数的构造函数。重写ThreadGroup类的uncaughtException()方法用来处理组内的线程抛出的异常:
public class MyThreadGroup extends ThreadGroup { public MyThreadGroup(String name) { super(name); }
-
重写uncaughtException()方法。当ThreadGroup类中的一个线程抛出异常时,调用此方法。在这种情况下,此方法记录异常以及抛出异常的线程信息,并展现到控制台上。同时,要注意此方法将中断ThreadGroup类中其它线程执行:
@Override public void uncaughtException(Thread t, Throwable e){ System.out.printf("The thread %s has thrown an Exception\n", t.getId()); e.printStackTrace(System.out); System.out.printf("Terminating the rest of the Threads\n"); interrupt(); }
-
创建名为Task的类,指定其实现Runnable接口:
public class Task implements Runnable {
-
实现run()方法。因为我们将用1000与随机数相除,直到除数为0 的时候抛出异常,所以引入AritmethicException异常:
@Override public void run() { int result; Random random = new Random(Thread.currentThread().getId()); while(true){ result = 1000 / ((int)(random.nextDouble() * 1000000000)); if(Thread.currentThread().isInterrupted()){ System.out.printf("%d : Interrupted\n", Thread.currentThread().getId()); return; } } }
-
现在实现范例的主类,创建名为Main的类并实现main()方法:
public class Main { public static void main(String[] args) {
-
首先,计算将要加载的线程数。我们使用Runtime类的availableProcessors() 方法(在此类中名为getRuntie()的静态方法,通过当前Java程序相关的运行环境对象获得)。此方法返回Java虚拟机运行的处理器个数,通常与运行此程序的计算机处理器个数相同:
int numberOfThreads = 2 * Runtime.getRuntime().availableProcessors();
-
创建MyThreadGroup类的对象:
MyThreadGroup threadGroup = new MyThreadGroup("MyThreadGroup");
-
创建Task类的对象:
Task task = new Task();
-
使用Task类创建已计算好个数的线程对象,开始运行:
for( int i = 0 ; i < numberOfThreads; i ++){ Thread t = new Thread(threadGroup, task); t.start(); }
-
然后,在控制台中输出ThreadGroup信息:
System.out.printf("Number of Threads: %d\n", threadGroup.activeCount()); System.out.printf("Information about the Thread Group\n"); threadGroup.list();
-
最后,在控制台中输出来自组内线程的状态信息:
Thread[] threads = new Thread[threadGroup.activeCount()]; threadGroup.enumerate(threads); for (int i = 0 ; i < threadGroup.activeCount(); i ++){ System.out.printf("Thread %s : %s\n", threads[i].getName(), threads[i].getState()); } } }
-
运行程序,查看结果。
工作原理
在下图中,可以看到ThreadGroup类中的list()方法以及生成的每个线程对象状态的输出信息:
因为ThreadGroup类存储线程对象和其他关联的ThreadGroup对象,所以它能够获得这些对象的所有信息(例如状态),以及执行所有对象成员的操作(例如中断)。
检验一个线程对象是如何抛出中断其它对象的异常:
当线程对象中抛出一个非捕获异常时,Java虚拟机寻找异常中三个可能的处理器。
首先是寻找线程的非捕获异常处理器,在“处理线程非受控异常”小节中讲解的。如果此处理器不存在,Java虚拟机寻找ThreadGroup类中的非捕获异常控制器,即本节中学到的。如果此方法也不存在,Java虚拟机会寻找默认的非捕获异常处理器,同样在“处理线程非受控异常”小节中讲解的。
如果三种处理器均不存在,Java虚拟器在控制台输出异常的堆栈跟踪,并且终止执行已经抛出异常的线程。
更多关注
- 本章中“处理线程非受控异常”小节。