从Java ExecutorService捕获线程异常

时间:2010-12-20 17:41:57

标签: java multithreading exception

我正在研究并行计算的软件开发框架JavaSeis.org。我需要一个强大的机制来报告线程异常。在开发过程中,了解异常的来源具有很高的价值,因此我想在过度报告方面犯错误。我也希望能够在线程中处理Junit4测试。方法是否合理或有更好的方法吗?

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThreadFailure {

  public static void main(String[] args) {
    int size = 1;
    ExecutorService exec = Executors.newFixedThreadPool(size);
    ThreadFailTask worker = new ThreadFailTask();
    Future<Integer> result = exec.submit(worker);
    try {
      Integer value = result.get();
      System.out.println("Result: " + value);
    } catch (Throwable t) {
      System.out.println("Caught failure: " + t.toString());
      exec.shutdownNow();
      System.out.println("Stack Trace:");
      t.printStackTrace();
      return;
    }
    throw new RuntimeException("Did not catch failure !!");
  }

  public static class ThreadFailTask implements Callable<Integer> {
    @Override
    public Integer call() {
      int nbuf = 65536;
      double[][] buf = new double[nbuf][nbuf];
      return new Integer((int) buf[0][0]);
    }
  }
}

6 个答案:

答案 0 :(得分:18)

考虑在execute()上调用submit()而不是ExecutorService。使用execute()调用的Thread会在失败时调用Thread.UncaughtExceptionHandler

只需在Thread.UncaughtExceptionHandler上设置一个ThreadFactory,然后在Threads上使用execute()而非{{1}调用您的工作}。

看看这个related stack overflow question

答案 1 :(得分:7)

在使用submit()时,我不相信有一个标准的“钩子”可以解决这些异常。但是,如果你需要支持submit()(这听起来合理,假设你使用Callable),你总是可以包装Callables和Runnables:

ExecutorService executor = new ThreadPoolExecutor(1, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>()) {
    @Override
    public <T> Future<T> submit(final Callable<T> task) {
        Callable<T> wrappedTask = new Callable<T>() {
            @Override
            public T call() throws Exception {
                try {
                    return task.call();
                }
                catch (Exception e) {
                    System.out.println("Oh boy, something broke!");
                    e.printStackTrace();
                    throw e;
                }
            }
        };

        return super.submit(wrappedTask);
    }
};

当然,这种方法只有在您构建ExecutorService的情况下才有效。此外,请记住覆盖所有三个submit()变体。

答案 2 :(得分:3)

如此线程What is the difference between submit and execute method with ThreadPoolExecutor中所述,使用execute只有在实现Runnable而不是Callable时才能工作,因为execute不能返回Future。

我认为在您的场景中,您应该构建未来对象,以便它也可以容纳异常内容。因此,如果出现异常,则构建错误消息对象。

答案 3 :(得分:1)

我的原始问题询问如何使用Java ExecutorService实现“健壮”线程异常处理。感谢Angelo和Greg指出异常处理如何与ExecutorService.submit()和Future.get()一起使用。我修改后的代码片段如下所示。我在这里学到的关键点是Future.get()捕获所有异常。如果线程被中断或取消,则会得到相应的异常,否则,异常将被包装并作为ExecutionException重新抛出。

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThreadFailure {

  public static void main(String[] args) {
    int size = 1;
    ExecutorService exec = Executors.newFixedThreadPool(size);
    ThreadFailTask worker = new ThreadFailTask();
    Future result = exec.submit(worker);
    try {
      Integer value = result.get();
      System.out.println("Result: " + value);
    } catch (ExecutionException ex) {
      System.out.println("Caught failure: " + ex.toString());
      exec.shutdownNow();
      return;
    } catch (InterruptedException iex) {
      System.out.println("Thread interrupted: " + iex.toString());
    } catch (CancellationException cex) {
      System.out.println("Thread cancelled: " + cex.toString());
    }
    exec.shutdownNow();
    throw new RuntimeException("Did not catch failure !!");
  }

  public static class ThreadFailTask implements Callable {
    @Override
    public Integer call() {
      int nbuf = 65536;
      double[][] buf = new double[nbuf][nbuf];
      return new Integer((int) buf[0][0]);
    }
  }
}

答案 4 :(得分:1)

我对其他答案没有太大的运气,因为我需要实际的异常实例本身,而不仅仅是打印的堆栈跟踪。对我而言,涉及问题“ThreadPoolExecutor#afterExecute()”的Why is UncaughtExceptionHandler not called by ExecutorService?的已接受答案有效。

请参阅以下示例代码:

List<Runnable> tasks = new LinkedList<>();
for (int i = 0; i < numThreads; ++i) {
    Runnable task = new Runnable() {
        @Override
        public void run() {
            throw new RuntimeException();
        }
    };

    tasks.add(task);
}

Optional<Throwable> opEmpty = Optional.empty();
/*
 * Use AtomicReference as a means of capturing the first thrown exception, since a
 * spawned thread can't "throw" an exception to the parent thread.
 */
final AtomicReference<Optional<Throwable>> firstThrownException =
        new AtomicReference<>(opEmpty);

/*
 * Use new ThreadPoolExecutor instead of Executors.newFixedThreadPool() so
 * that I can override afterExecute() for the purposes of throwing an
 * exception from the test thread if a child thread fails.
 */
ExecutorService execSvc = new ThreadPoolExecutor(numThreads, numThreads,
            0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()) {
    @Override
    public void afterExecute(Runnable task, Throwable failureCause) {
        if(failureCause == null) {
            // The Runnable completed successfully.
            return;
        }
        // only sets the first exception because it will only be empty on the first call.
        firstThrownException.compareAndSet(Optional.<Throwable>empty(), Optional.of(failureCause));
    }
};

for (Runnable task : tasks) {
    execSvc.execute(task);
}
execSvc.shutdown();
execSvc.awaitTermination(1, TimeUnit.HOURS);

assertEquals(firstThrownException.get(), Optional.empty());

答案 5 :(得分:0)

要处理 ExecutorService 中的例外情况,您必须利用可调用未来

请观看以下视频了解详情。希望这会对你有帮助。

VIDEO: Callable and Future(11 min)