七、创建和运行一个后台线程
Java中有一种特别的线程叫做 deamon(后台) 线程。这类线程具有非常低的权限,并且只有在同一个程序中没有其他的正常线程在运行时才会运行。注意:当一个程序中只剩下后台线程时,JVM会终结所有的后台线程并结束程序。
由于这个特性,后台线程一般用于为同一个程序中的其他正常线程提供服务。这种后台线程一般都有一个无限的循环在等待请求服务或者执行请求的任务。由于不知道它们何时可以获得CPU的调用执行,同时在没有其他正常线程的情况下会被JVM终结,所以后台线程不能用于执行重要的任务。后台线程的一个典型应用是Java中的垃圾回收器。
本秘诀中,我们将学习如何创建具有两个线程的程序:一个正常线程在一个队列上写事件,另一个后台线程负责清理该队列上10秒之前生成的事件。
public class CleanerTask extendsThread {private Dequedeque;public CleanerTask(Dequedeque) {this.deque =deque;
setDaemon(true);
}
@Overridepublic voidrun() {while (true) {
Date date= newDate();
clean(date);
}
}private voidclean(Date date) {longdifference;booleandelete;if (deque.size() == 0) {return;
}
delete= false;do{
Event e=deque.getLast();
difference= date.getTime() -e.getDate().getTime();if (difference > 1000) {
System.out.println("Cleaner: "+ e.getEvent() +"\n");
deque.removeLast();
delete= true;
}
}while (difference > 1000);if(delete) {
System.out.printf("Cleaner: Size of the queue: "+ deque.size() +"\n");
}
}
}public classEvent {privateDate date;privateString event;publicDate getDate() {returndate;
}public voidsetDate(Date date) {this.date =date;
}publicString getEvent() {returnevent;
}public voidsetEvent(String event) {this.event =event;
}
}public classMain {public static voidmain(String[] args) {
Deque deque = new ArrayDeque();
WriterTask writer= newWriterTask(deque);for (int i = 0; i < 3; i++) {
Thread thread= newThread(writer);
thread.start();
}
CleanerTask cleaner= newCleanerTask(deque);
cleaner.start();
}
}public class WriterTask implementsRunnable{private Dequedeque;public WriterTask(Dequedeque) {this.deque =deque;
}
@Overridepublic voidrun() {for (int i = 1; i < 20; i++) {
Event event= newEvent();
event.setDate(newDate());
event.setEvent(String.format("The thread %s has generated " +
"an event", Thread.currentThread().getId()));
deque.addFirst(event);try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:setDeamon() 方法必须在 start() 方法前调用才有效。一旦线程启动,就不能修改其是否为后台线程了。同时,可以使用 isDeamon() 方法来判断线程是否为后台线程。
八、处理线程中未控制的异常
Java中有两类异常:
(A) 检测异常:必须捕获或者抛出。
(B) 不检测异常:可以不捕获。
因为 Thread 对象的 run() 方法不能抛出异常,所以出现在此方法中的检测异常都必须捕获。当 run() 方法中抛出不检测异常时,Thread 对象的默认行为是写线程堆栈信息到控制台,然后退出程序。
幸运的是,Java提供了让我们可以捕获和转换未检测异常的机制,使得我们可以避免由于 run() 方法中异常的抛出而导致的程序终结。
Java中有一个 UncaughtExceptionHandler 接口来处理未捕获的异常。
public class ExceptionHandler implementsUncaughtExceptionHandler{
@Overridepublic voiduncaughtException(Thread t, Throwable e) {
System.out.println("An exception has been captured");
System.out.println("Thread: " +t.getId());
System.out.println("Exception: " + e.getClass().getName() + ": "
+e.getMessage());
System.out.println("Stack Trace: ");
e.printStackTrace(System.out);
System.out.println("Thread status: " +t.getState());
}
}public classMain {public static voidmain(String[] args) {
Task task= newTask();
Thread thread= newThread(task);
thread.setUncaughtExceptionHandler(newExceptionHandler());
thread.start();
}
}public class Task implementsRunnable {
@Overridepublic voidrun() {
Integer.parseInt("TTT");
}
}
当线程中有一个异常抛出来时,JVM首先检查该线程是否有设置线程异常处理器,如果有就调用该处理器处理该异常。如果没有,JVM的默认行为是打印线程堆栈信息到控制台,同时推出程序。
注意:Thread 类有一个静态方法 setDefaultUncaughtExceptionHandler() 来为程序中所有的未捕获异常设置处理器。当线程中抛出一个未捕获的异常时:JVM首先查看该线程是否有设置对应的线程异常处理器,如果没有再查看线程所属的线程组是否有设置好线程异常处理器,如果还没有再查看程序是否有设置默认线程异常处理器。
如果没有任何异常处理器存在,JVM就会执行默认行为:写线程栈帧信息到控制台,终止程序的运行。
重要:本系列翻译文档也会在本人的微信公众号(此山是我开)第一时间发布,欢迎大家关注。