2009-12-03 25 views
41

我偶然发现了一个问题,可以总结如下:为什么ExecutorService不调用UncaughtExceptionHandler?

当我手动创建线程时(即通过实例化java.lang.Thread),UncaughtExceptionHandler被适当调用。但是,当我使用带有ThreadFactoryExecutorService时,处理程序被省略。我错过了什么?

public class ThreadStudy { 

private static final int THREAD_POOL_SIZE = 1; 

public static void main(String[] args) { 

    // create uncaught exception handler 

    final UncaughtExceptionHandler exceptionHandler = new UncaughtExceptionHandler() { 

     @Override 
     public void uncaughtException(Thread t, Throwable e) { 
      synchronized (this) { 
       System.err.println("Uncaught exception in thread '" + t.getName() + "': " + e.getMessage()); 
      } 
     } 
    }; 

    // create thread factory 

    ThreadFactory threadFactory = new ThreadFactory() { 

     @Override 
     public Thread newThread(Runnable r) { 
      // System.out.println("creating pooled thread"); 
      final Thread thread = new Thread(r); 
      thread.setUncaughtExceptionHandler(exceptionHandler); 
      return thread; 
     } 
    }; 

    // create Threadpool 

    ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE, threadFactory); 

    // create Runnable 

    Runnable runnable = new Runnable() { 

     @Override 
     public void run() { 
      // System.out.println("A runnable runs..."); 
      throw new RuntimeException("Error in Runnable"); 
     } 
    }; 

    // create Callable 

    Callable<Integer> callable = new Callable<Integer>() { 

     @Override 
     public Integer call() throws Exception { 
      // System.out.println("A callable runs..."); 
      throw new Exception("Error in Callable"); 
     } 
    }; 

    // a) submitting Runnable to threadpool 
    threadPool.submit(runnable); 

    // b) submit Callable to threadpool 
    threadPool.submit(callable); 

    // c) create a thread for runnable manually 
    final Thread thread_r = new Thread(runnable, "manually-created-thread"); 
    thread_r.setUncaughtExceptionHandler(exceptionHandler); 
    thread_r.start(); 

    threadPool.shutdown(); 
    System.out.println("Done."); 
} 
} 

我期望:消息的三倍“未捕获异常...”

我得到:消息一次(由手动创建的线程触发)。

在Windows 7和Mac OS X 10.5上使用Java 1.6重现。

+0

也许这项工作,对我来说这是确定它是不是一个FixedThreadPool ,但SingleThreadPool ...但你明白http://stackoverflow.com/a/44007121/8020889 – 2017-05-16 16:48:39

回答

35

因为异常不会被取消。

您的ThreadFactory产生的线程没有直接给予您的Runnable或Callable。相反,你得到的Runnable是一个内部的Worker类,例如见ThreadPoolExecutor $ Worker。尝试System.out.println()对您的示例中给予newThread的Runnable。

此工作人员捕获您提交的作业中的任何RuntimeException。

您可以在ThreadPoolExecutor#afterExecute方法中得到例外。

+10

虽然这个答案是正确的,正确实施afterExecute是棘手的。请参阅[溢出问题](http://stackoverflow.com/questions/2248131/handling-exceptions-from-java-executorservice-tasks),了解如何正确实施此处提出的解决方案的示例。 – 2011-05-09 15:10:54

+1

这是一篇关于线程代码中的异常处理的文章,包括'Future'和'ExecutorService':http://literatejava.com/threading/silent-thread-death-unhandled-exceptions/ – 2014-05-10 03:21:25

5

我刚刚浏览了我的旧问题,并认为我可能会共享我实施的解决方案以防止某人(或者我错过了一个错误)。

import java.lang.Thread.UncaughtExceptionHandler; 
import java.util.concurrent.Callable; 
import java.util.concurrent.Delayed; 
import java.util.concurrent.ExecutionException; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.FutureTask; 
import java.util.concurrent.RunnableScheduledFuture; 
import java.util.concurrent.ScheduledThreadPoolExecutor; 
import java.util.concurrent.ThreadFactory; 
import java.util.concurrent.TimeUnit; 


/** 
* @author Mike Herzog, 2009 
*/ 
public class ExceptionHandlingExecuterService extends ScheduledThreadPoolExecutor { 

    /** My ExceptionHandler */ 
    private final UncaughtExceptionHandler exceptionHandler; 

    /** 
    * Encapsulating a task and enable exception handling. 
    * <p> 
    * <i>NB:</i> We need this since {@link ExecutorService}s ignore the 
    * {@link UncaughtExceptionHandler} of the {@link ThreadFactory}. 
    * 
    * @param <V> The result type returned by this FutureTask's get method. 
    */ 
    private class ExceptionHandlingFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> { 

     /** Encapsulated Task */ 
     private final RunnableScheduledFuture<V> task; 

     /** 
     * Encapsulate a {@link Callable}. 
     * 
     * @param callable 
     * @param task 
     */ 
     public ExceptionHandlingFutureTask(Callable<V> callable, RunnableScheduledFuture<V> task) { 
      super(callable); 
      this.task = task; 
     } 

     /** 
     * Encapsulate a {@link Runnable}. 
     * 
     * @param runnable 
     * @param result 
     * @param task 
     */ 
     public ExceptionHandlingFutureTask(Runnable runnable, RunnableScheduledFuture<V> task) { 
      super(runnable, null); 
      this.task = task; 
     } 

     /* 
     * (non-Javadoc) 
     * @see java.util.concurrent.FutureTask#done() The actual exception 
     * handling magic. 
     */ 
     @Override 
     protected void done() { 
      // super.done(); // does nothing 
      try { 
       get(); 

      } catch (ExecutionException e) { 
       if (exceptionHandler != null) { 
        exceptionHandler.uncaughtException(null, e.getCause()); 
       } 

      } catch (Exception e) { 
       // never mind cancelation or interruption... 
      } 
     } 

     @Override 
     public boolean isPeriodic() { 
      return this.task.isPeriodic(); 
     } 

     @Override 
     public long getDelay(TimeUnit unit) { 
      return task.getDelay(unit); 
     } 

     @Override 
     public int compareTo(Delayed other) { 
      return task.compareTo(other); 
     } 

    } 

    /** 
    * @param corePoolSize The number of threads to keep in the pool, even if 
    *  they are idle. 
    * @param eh Receiver for unhandled exceptions. <i>NB:</i> The thread 
    *  reference will always be <code>null</code>. 
    */ 
    public ExceptionHandlingExecuterService(int corePoolSize, UncaughtExceptionHandler eh) { 
     super(corePoolSize); 
     this.exceptionHandler = eh; 
    } 

    @Override 
    protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) { 
     return new ExceptionHandlingFutureTask<V>(callable, task); 
    } 

    @Override 
    protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) { 
     return new ExceptionHandlingFutureTask<V>(runnable, task); 
    } 
} 
2

有一点点的解决方法。 运行中的方法,你可以抓住每一个例外,后来就做这样的事情

Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex); 
//or 
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex); 

这将“注册”为抛给你uncoughtExceptionHandler或到defualt uncought异常处理当前异常。 您可以随时重新引发池工作者的捕获异常。

20

例外,它们被提交给ExecutorService#submit任务扔得到包裹成ExcecutionException,并由Future.get()方法重新排列。这是因为执行者认为异常是任务结果的一部分。

如果您通过​​方法提交任务,而该方法源自Executor接口,则会通知UncaughtExceptionHandler

+3

这是一个令人惊讶的干净和简单的解决方案。谢谢! – 2015-08-17 08:55:48

+0

迄今为止最简单的解决方案。 有关此行为的更多信息,请参考以下答案:https://stackoverflow.com/a/3986509/2874005 – Tom 2017-09-17 14:50:59

+0

在我的实验中,如果ExecutorService已关闭,并且最后一个存活的工作线程遇到未捕获的异常,似乎在调用处理程序之前终止。我无法从'ThreadPoolExecutor'看到为什么会这样。 – erickson 2017-11-02 22:49:07

15
从书 Java并发

报价在实践(163页),希望这有助于

有点混乱,从任务抛出的异常使其对未捕获 异常处理程序仅适用于执行提交的任务;对于提交的提交 的任务,任何抛出的异常(被检查与否)都被认为是 任务返回状态的一部分。如果通过提交提交的任务终止并产生异常,则 将由Future.get重新包装,并包装在ExecutionException中。

这里是例子:

public class Main { 

public static void main(String[] args){ 


    ThreadFactory factory = new ThreadFactory(){ 

     @Override 
     public Thread newThread(Runnable r) { 
      // TODO Auto-generated method stub 
      final Thread thread =new Thread(r); 

      thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 

       @Override 
       public void uncaughtException(Thread t, Throwable e) { 
        // TODO Auto-generated method stub 
        System.out.println("in exception handler"); 
       } 
      }); 

      return thread; 
     } 

    }; 

    ExecutorService pool=Executors.newSingleThreadExecutor(factory); 
    pool.execute(new testTask()); 

} 



private static class testTask implements Runnable { 

    @Override 
    public void run() { 
     // TODO Auto-generated method stub 
     throw new RuntimeException(); 
    } 

} 

我用执行提交任务和“异常处理程序”控制台输出