捕获java线程池执行任务抛出的异常 -凯发k8网页登录

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

   :: 凯发k8网页登录首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  111 随笔 :: 10 文章 :: 2680 评论 :: 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)。

原文地址:



©2009-2014 imxylz
|求贤若渴
posted on 2013-08-05 16:45 imxylz 阅读(29625) 评论(6)  编辑  收藏 所属分类: java concurrency
# re: 捕获java线程池执行任务抛出的异常 2013-08-08 10:34
v5的大湿重出江湖

@test
public void testthread(){
threadtest tt = new threadtest();
tt.setuncaughtexceptionhandler(new localuncaughtexceptionhandler());
tt.start();
}  回复  
  

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

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

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

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

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


©2009-2014
网站地图