1.关于异常
线程池在执行execute方法和submit任务的时候,如果任务中抛出了异常,外部线程无法感知,所以要做一些措施来进行处理,下面就说一下产生这种效果的原因以及一些可用的处理方法。
关于异常的处理内容参考了文章并进行了相关补充:
ExecutorService-10个要诀和技巧
1.execute执行的任务抛出异常
1.原理
测试代码:
public static void main(String[] args) {
Executors.newFixedThreadPool(1);
try {
Executors.newFixedThreadPool(1).execute(new Runnable() {
@Override public void run() {
int i=1/0;
}
});
} catch (Exception e) {
System.out.println("catch!!!!");
} finally {
System.out.println("normal");
}
}
这个异常会被内部吞掉的,不会被catch块catch到。
但是程序会用System.err和e.printStackTrace方式打印异常栈信息,是由下面这个方法触发的:
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
看注释,这个方法是用来分发处理一个uncaught的异常交由UncaughtExceptionHandler来处理,是由JVM底层来调用的。
具体的实现在ThreadGroup类中,有个uncaughtException方法,
/**
* Called by the Java Virtual Machine when a thread in this
* thread group stops because of an uncaught exception, and the thread
* does not have a specific {@link Thread.UncaughtExceptionHandler}
* installed.
* <p>
* Applications can override this method in subclasses of
* <code>ThreadGroup</code> to provide alternative handling of
* uncaught exceptions.
*
* @param t the thread that is about to exit.
* @param e the uncaught exception.
* @since JDK1.0
*/
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
先看注释,该方法是由JVM调用,当一个线程由于一个uncaught异常而导致线程终止,而且该线程没有指定一个UncaughtExceptionHandler。
在else if块中会用System.err和e.printStackTrace配合打印异常信息。
这种情况为了保证我们的程序在运行时能够感知异常,我们可以在我们的task的run方法中用try catch把语句包起来,然后在catch记录日志和监控。注意这里既是把线程包装一下重新跑出去也是没用的。
2.解决方案
自己把线程封装一下
/**
* 第一种实现自己定义线程,同时自己定义一个ThreadFactory的实现呢
*/
static class MyAppThread extends Thread {
public static final String DEFAULT_NAME = "MyAppThread";
private static final AtomicInteger created = new AtomicInteger();
private static final AtomicInteger alive = new AtomicInteger();
public MyAppThread(Runnable r) {
this(r, DEFAULT_NAME);
}
public MyAppThread(Runnable runnable, String name) {
super(runnable, name + "-" + created.incrementAndGet());
this.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t,
Throwable e) {
logger.error("UNCAUGHT1 in thread " + t.getName(), e);
}
});
}
public void run() {
System.out.println("Created " + getName());
try {
alive.incrementAndGet();
super.run();
} finally {
alive.decrementAndGet();
System.out.println("Exiting " + getName());
}
}
public static int getThreadsCreated() {
return created.get();
}
public static int getThreadsAlive() {
return alive.get();
}
}
自己封装线程的工厂类
public class MyThreadFactory2 {
public Thread newThread(Runnable runnable) {
Thread ret = Executors.defaultThreadFactory().newThread(runnable);
ret.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t,
Throwable e) {
logger.error("UNCAUGHT2 in thread " + t.getName(), e);
}
});
return ret;
}
}
在构造线程池的时候,作为参数传递我们自己封装的线程工厂。
2.submit方法提交的任务中抛出异常
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
线程池的submit方法提交的任务是一个FutureTask,它既含有Runnable的任务属性也含有Future的属性。FutureTask的run方法中调用的是内部实现的AbstractQueuedSynchronizer的innerRun方法,
void innerRun() {
if (!compareAndSetState(READY, RUNNING))
return;
runner = Thread.currentThread();
if (getState() == RUNNING) { // recheck after setting thread
V result;
try {
result = callable.call();
} catch (Throwable ex) {
setException(ex);
return;
}
set(result);
} else {
releaseShared(0); // cancel
}
}
所以call方法抛出异常的话,会被catch捕获并用setException进行异常的设置。在设置异常之后,用future的get(内部是Sync的innerGet方法)方法获取结果的时候会抛出异常,
V innerGet() throws InterruptedException, ExecutionException {
acquireSharedInterruptibly(0);
if (getState() == CANCELLED)
throw new CancellationException();
if (exception != null)
throw new ExecutionException(exception);
return result;
}
所以为了确保外部感知到这个异常,有两个方法:
1.一定要是用future的阻塞的get方法来获取结果。如果调用带超时时间的get方法,如果异常在超时之后发生的话,外部也无法感知到这个异常。如果业务上在超时之后已经不关心这个异常了的话,也可以只对超时进行监控和日志输出。
2.对call方法内部进行try catch包裹,在有异常发生的时候,设置一个特殊值来表示异常发生情况下的返回,同时记录日志和监控。