xylz,imxylz

关注后端架构、中间件、分布式和并发编程

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  111 随笔 :: 10 文章 :: 2679 评论 :: 0 Trackbacks

Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常,

public interface Runnable {

    
public abstract void run();
}

那么如果 run() 方法中抛出了RuntimeException,将会怎么处理了?

通常java.lang.Thread对象运行设置一个默认的异常处理方法:

java.lang.Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)

而这个默认的静态全局的异常捕获方法时输出堆栈。

当然,我们可以覆盖此默认实现,只需要一个自定义的java.lang.Thread.UncaughtExceptionHandler接口实现即可。

public interface UncaughtExceptionHandler {

    
void uncaughtException(Thread t, Throwable e);
}

而在线程池中却比较特殊。默认情况下,线程池 java.util.concurrent.ThreadPoolExecutor 会Catch住所有异常, 当任务执行完成(java.util.concurrent.ExecutorService.submit(Callable))获取其结果 时(java.util.concurrent.Future.get())会抛出此RuntimeException。

/**
 * Waits if necessary for the computation to complete, and then
 * retrieves its result.
 *
 * 
@return the computed result
 * 
@throws CancellationException if the computation was cancelled
 * 
@throws ExecutionException if the computation threw an exception
 * 
@throws InterruptedException if the current thread was interrupted while waiting
 
*/
V get() 
throws InterruptedException, ExecutionException;

其中 ExecutionException 异常即是java.lang.Runnable 或者 java.util.concurrent.Callable 抛出的异常。

也就是说,线程池在执行任务时捕获了所有异常,并将此异常加入结果中。这样一来线程池中的所有线程都将无法捕获到抛出的异常。 从而无法通过设置线程的默认捕获方法拦截的错误异常。

也不同通过自定义线程来完成异常的拦截。

好在java.util.concurrent.ThreadPoolExecutor 预留了一个方法,运行在任务执行完毕进行扩展(当然也预留一个protected方法beforeExecute(Thread t, Runnable r)):

protected void afterExecute(Runnable r, Throwable t) { } 

此方法的默认实现为空,这样我们就可以通过继承或者覆盖ThreadPoolExecutor 来达到自定义的错误处理。

解决办法如下:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(111001, TimeUnit.MINUTES, //
        new ArrayBlockingQueue<Runnable>(10000),//
        new DefaultThreadFactory()) {

    
protected void afterExecute(Runnable r, Throwable t) {
        
super.afterExecute(r, t);
        printException(r, t);
    }
};

private static void printException(Runnable r, Throwable t) {
    
if (t == null && r instanceof Future<?>) {
        
try {
            Future
<?> future = (Future<?>) r;
            
if (future.isDone())
                future.get();
        } 
catch (CancellationException ce) {
            t 
= ce;
        } 
catch (ExecutionException ee) {
            t 
= ee.getCause();
        } 
catch (InterruptedException ie) {
            Thread.currentThread().interrupt(); 
// ignore/reset
        }
    }
    
if (t != null)
        log.error(t.getMessage(), t);
}

此办法的关键在于,事实上 afterExecute 并不会总是抛出异常 Throwable t,通过查看源码得知,异常是封装在此时的Future对象中的, 而此Future对象其实是一个java.util.concurrent.FutureTask的实现,默认的run方法其实调用的 java.util.concurrent.FutureTask.Sync.innerRun()。

void innerRun() {
    if (!compareAndSetState(0, RUNNING))
        return;
    try {
        runner = Thread.currentThread();
        if (getState() == RUNNING) // recheck after setting thread
            innerSet(callable.call());
        else
            releaseShared(0); // cancel
    } catch (Throwable ex) {
        innerSetException(ex);
    }
}

void innerSetException(Throwable t) {
    for (;;) {
        int s = getState();
        if (s == RAN)
            return;
        if (s == CANCELLED) {
            // aggressively release to set runner to null,
            
// in case we are racing with a cancel request
            
// that will try to interrupt runner
            releaseShared(0);
            return;
        }
        if (compareAndSetState(s, RAN)) {
            exception = t;
            result = null;
            releaseShared(0);
            done();
            return;
        }
    }
}

这里我们可以看到它吃掉了异常,将异常存储在java.util.concurrent.FutureTask.Sync的exception字段中:

/** The exception to throw from get() */
private Throwable exception;

当我们获取异步执行的结果时, java.util.concurrent.FutureTask.get()

public V get() throws InterruptedException, ExecutionException {
    
return sync.innerGet();
}

java.util.concurrent.FutureTask.Sync.innerGet()

V innerGet() throws InterruptedException, ExecutionException {
    acquireSharedInterruptibly(
0);
    
if (getState() == CANCELLED)
        
throw new CancellationException();
    
if (exception != null)
        
throw new ExecutionException(exception);
    
return result;
}

异常就会被包装成ExecutionException异常抛出。

也就是说当我们想线程池 ThreadPoolExecutor(java.util.concurrent.ExecutorService)提交任务时, 如果不理会任务结果(Feture.get()),那么此异常将被线程池吃掉。

<T> Future<T> submit(Callable<T> task);
Future
<?> submit(Runnable task);

而java.util.concurrent.ScheduledThreadPoolExecutor是继承ThreadPoolExecutor的,因此情况类似。

结论,通过覆盖ThreadPoolExecutor.afterExecute 方法,我们才能捕获到任务的异常(RuntimeException)。

原文地址:http://imxylz.com/blog/2013/08/02/handling-the-uncaught-exception-of-java-thread-pool/



©2009-2014 IMXYLZ |求贤若渴
posted on 2013-08-05 16:45 imxylz 阅读(18499) 评论(6)  编辑  收藏 所属分类: Java Concurrency

评论

# re: 捕获Java线程池执行任务抛出的异常 2013-08-08 10:34 陕西BOY
V5的大湿重出江湖

@Test
public void testThread(){
ThreadTest tt = new ThreadTest();
tt.setUncaughtExceptionHandler(new LocalUncaughtExceptionHandler());
tt.start();
}  回复  更多评论
  

# re: 捕获Java线程池执行任务抛出的异常[未登录] 2013-08-15 10:24 Ryan
呵呵~~哥们的博客,尤其是java并发这块,很好很好哦。  回复  更多评论
  

# re: 捕获Java线程池执行任务抛出的异常[未登录] 2013-11-08 10:31 w
dd  回复  更多评论
  

# re: 捕获Java线程池执行任务抛出的异常 2015-05-12 22:59 liubey
我想问下LZ,如果主线程想拿到子线程的异常,比如展示给界面,该怎么做=。=  回复  更多评论
  

# re: 捕获Java线程池执行任务抛出的异常 2015-05-13 10:37 imxylz
@liubey
友好的做法是子线程不抛出异常,返回不同的结果,或者将异常封装到return对象中。父对象根据此结果/异常封装友好的提示给界面。  回复  更多评论
  

# re: 捕获Java线程池执行任务抛出的异常 2015-05-26 13:55 liubey
@imxylz
谢谢指点,你这种方式更优雅些,我自己是new了个exceptionQueue,new线程的时候set进去,然后执行完子线程后查看这个Queue  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航:
 

©2009-2014 IMXYLZ