首页 > 代码库 > Java 并发编程之任务取消(八)

Java 并发编程之任务取消(八)

处理非正常的线程中止

当单线程的控制台程序由于 发生了一个未捕获的异常而终止时,程序将停止运行,并产生与程序正常输出非常不同的栈追踪信息,这种情况是很容易理解的。然而,如果并发程序中的某个线程发生故障,那么通常不会如此明显。在控制台中可能会输出栈追踪信息,但没有人会观察控制台。此外,当线程发生故障时,应用程序可能看起来仍然 在工作,所以这个失败很可能被忽略。下面要讲的问题就是监测并防止在程序中“遗漏”线程的方法 。

导致线程提前死亡的最主要原因就是RuntimeException。

我们可以在线程池内部构建一个工作者线程。如果任务抛出一个未检查异常,那么它将使线程终结。框架可能会用新的线程代替这个工作线程,也可能不会。因为线程池正在关闭,或者 已经有足够多的线程满足需要。

当编写一个向线程池提交任务的工作者线程类时,或者调用不可信的外部代码时(例如动态加载的插件)。使用这些方法中的某一种可以避免某个编写得糟糕的任务或插件不会影响调用它的整个线程。

	public void run() {
		Throwable thrown = null;
		try {
			while (!isInterrupted())
				runTask(getTaskFromWorkQueue());
		} catch (Throwable e) {
			thrown = e;
		} finally {
			threadExited(this, thrown);
		}
	}

未捕获的异常的处理

上面是一种主动方法来解决未检查异常,在Thread API中同样提供了UncaughtExceptionHandler,它能检测出来 某个纯种由于未捕获异常而终结的情况。这两种情况是互补的。通过将两者结合在一起,能有效地防止线程泄漏的问题


当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器。如果没有提供任何异常处理器。那么默认的行为是将栈追踪信息输出到System.err。

public class TestThread extends Thread {

	@Override
	public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
		// TODO Auto-generated method stub
		super.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {

			@Override
			public void uncaughtException(Thread t, Throwable e) {
				// TODO Auto-generated method stub

			}
		});
	}

}
最常见的响应方式是将一个错误信息以及相应的栈追踪信息写入应用程序日志中。还可以采用更直接的响应。例如尝试重新启动线程,关闭应用程序,或者执行其他修复或诊断等操作。

import java.util.logging.Level;
import java.util.logging.Logger;

public class UEHLogger implements Thread.UncaughtExceptionHandler {

	@Override
	public void uncaughtException(Thread t, Throwable e) {
		// TODO Auto-generated method stub
		Logger logger = Logger.getAnonymousLogger();
		logger.log(Level.SEVERE,"thread terminted with exception" + t.getName(), e);
	}

}

要为线程池中的所有线程设置一个UncaughtExceptionHandler。需要为ThreadPoolExecutor的构造函数提供一个ThreadFactory。为的是实现 只有线程的所有者能够改变线程的UncaughtExceptionHandler


import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExampleThreadpool extends ThreadPoolExecutor {

	public ExampleThreadpool(int corePoolSize, int maximumPoolSize,
			long keepAliveTime, TimeUnit unit,
			BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
				threadFactory);
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		ThreadFactory tf = new ThreadFactory() {

			@Override
			public Thread newThread(Runnable r) {
				// TODO Auto-generated method stub
				Thread t = new Thread(r);
				t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {

					@Override
					public void uncaughtException(Thread t, Throwable e) {
						// TODO Auto-generated method stub

					}
				});
				return t;
			}
		};
		new ExampleThreadpool(corePoolSize, maximumPoolSize, keepAliveTime,
				unit, workQueue, tf);
	}
}
只举了ThreadFactory的例子。

另外需要注意的是,只有通过execute提交的任务,才能将它报出的异常交给未捕获异常处理器。而通过submit提交的任务,无论是抛出的未检查异常还是已检查异常,都将被 认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么 这个异常将被Future.get封装在ExecutionException中重新抛出、


参考资料:《java 并发编程实战》

Java 并发编程之任务取消(八)