[英]Handling exceptions from Java ExecutorService tasks
我正在嘗試使用 Java 的ThreadPoolExecutor
類來運行具有固定線程數的大量重量級任務。 每個任務都有很多地方可能由於異常而失敗。
我將ThreadPoolExecutor
子類化,並重寫了afterExecute
方法,該方法應該提供運行任務時遇到的任何未捕獲的異常。 但是,我似乎無法讓它工作。
例如:
public class ThreadPoolErrors extends ThreadPoolExecutor {
public ThreadPoolErrors() {
super( 1, // core threads
1, // max threads
1, // timeout
TimeUnit.MINUTES, // timeout units
new LinkedBlockingQueue<Runnable>() // work queue
);
}
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if(t != null) {
System.out.println("Got an error: " + t);
} else {
System.out.println("Everything's fine--situation normal!");
}
}
public static void main( String [] args) {
ThreadPoolErrors threadPool = new ThreadPoolErrors();
threadPool.submit(
new Runnable() {
public void run() {
throw new RuntimeException("Ouch! Got an error.");
}
}
);
threadPool.shutdown();
}
}
該程序的輸出是“一切正常——情況正常!” 即使提交給線程池的唯一 Runnable 拋出異常。 有什么線索嗎?
謝謝!
警告:應該注意的是,這個解決方案會阻塞調用線程。
如果要處理任務拋出的異常,那么通常使用Callable
而不是Runnable
更好。
Callable.call()
允許拋出已檢查的異常,這些異常會傳播回調用線程:
Callable task = ...
Future future = executor.submit(task);
try {
future.get();
} catch (ExecutionException ex) {
ex.getCause().printStackTrace();
}
如果Callable.call()
拋出異常,這將被包裝在ExecutionException
並由Future.get()
拋出。
這可能比子類化ThreadPoolExecutor
更可取。 如果異常是可恢復的,它還使您有機會重新提交任務。
從文檔:
注意:當Action顯式或通過submit等方法包含在任務(如FutureTask)中時,這些任務對象會捕獲並維護計算異常,因此不會導致突然終止,內部異常不會傳遞給該方法.
當你提交一個 Runnable 時,它會被包裹在一個 Future 中。
你的 afterExecute 應該是這樣的:
public final class ExtendedExecutor extends ThreadPoolExecutor {
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, 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();
}
}
if (t != null) {
System.out.println(t);
}
}
}
這種行為的解釋在afterExecute的javadoc 中是正確的:
注意:當Action顯式或通過submit等方法包含在任務(如FutureTask)中時,這些任務對象會捕獲並維護計算異常,因此不會導致突然終止,內部異常不會傳遞給該方法.
我通過包裝提交給執行程序的提供的 runnable 來解決它。
CompletableFuture.runAsync(() -> {
try {
runnable.run();
} catch (Throwable e) {
Log.info(Concurrency.class, "runAsync", e);
}
}, executorService);
我正在使用VerboseRunnable
-log 中的VerboseRunnable
類,它吞下所有異常並記錄它們。 非常方便,例如:
import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
new VerboseRunnable(
Runnable() {
public void run() {
// the code, which may throw
}
},
true // it means that all exceptions will be swallowed and logged
),
1, 1, TimeUnit.MILLISECONDS
);
另一種解決方案是使用ManagedTask和ManagedTaskListener 。
您需要實現接口ManagedTask的Callable或Runnable 。
方法getManagedTaskListener
返回您想要的實例。
public ManagedTaskListener getManagedTaskListener() {
然后在ManagedTaskListener 中實現taskDone
方法:
@Override
public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable exception) {
if (exception != null) {
LOGGER.log(Level.SEVERE, exception.getMessage());
}
}
有關托管任務生命周期和偵聽器的更多詳細信息。
這有效
它會創建一個單線程的Executor,可以得到很多任務; 並將等待當前一個結束執行以開始下一個
在 uncaugth 錯誤或異常的情況下, uncaughtExceptionHandler將捕獲它
public final class SingleThreadExecutorWithExceptions { public static ExecutorService newSingleThreadExecutorWithExceptions(final Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { ThreadFactory factory = (Runnable runnable) -> { final Thread newThread = new Thread(runnable, "SingleThreadExecutorWithExceptions"); newThread.setUncaughtExceptionHandler( (final Thread caugthThread,final Throwable throwable) -> { uncaughtExceptionHandler.uncaughtException(caugthThread, throwable); }); return newThread; }; return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), factory){ protected void afterExecute(Runnable runnable, Throwable throwable) { super.afterExecute(runnable, throwable); if (throwable == null && runnable instanceof Future) { try { Future future = (Future) runnable; if (future.isDone()) { future.get(); } } catch (CancellationException ce) { throwable = ce; } catch (ExecutionException ee) { throwable = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); // ignore/reset } } if (throwable != null) { uncaughtExceptionHandler.uncaughtException(Thread.currentThread(),throwable); } } }); } private static class FinalizableDelegatedExecutorService extends DelegatedExecutorService { FinalizableDelegatedExecutorService(ExecutorService executor) { super(executor); } protected void finalize() { super.shutdown(); } } /** * A wrapper class that exposes only the ExecutorService methods * of an ExecutorService implementation. */ private static class DelegatedExecutorService extends AbstractExecutorService { private final ExecutorService e; DelegatedExecutorService(ExecutorService executor) { e = executor; } public void execute(Runnable command) { e.execute(command); } public void shutdown() { e.shutdown(); } public List shutdownNow() { return e.shutdownNow(); } public boolean isShutdown() { return e.isShutdown(); } public boolean isTerminated() { return e.isTerminated(); } public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return e.awaitTermination(timeout, unit); } public Future submit(Runnable task) { return e.submit(task); } public Future submit(Callable task) { return e.submit(task); } public Future submit(Runnable task, T result) { return e.submit(task, result); } public List> invokeAll(Collection> tasks) throws InterruptedException { return e.invokeAll(tasks); } public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { return e.invokeAll(tasks, timeout, unit); } public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { return e.invokeAny(tasks); } public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return e.invokeAny(tasks, timeout, unit); } } private SingleThreadExecutorWithExceptions() {} }
如果您想監控任務的執行,您可以旋轉 1 或 2 個線程(可能更多取決於負載)並使用它們從 ExecutionCompletionService 包裝器中獲取任務。
如果您的ExecutorService
來自外部源(即不可能子類化ThreadPoolExecutor
並覆蓋afterExecute()
),您可以使用動態代理來實現所需的行為:
public static ExecutorService errorAware(final ExecutorService executor) {
return (ExecutorService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[] {ExecutorService.class},
(proxy, method, args) -> {
if (method.getName().equals("submit")) {
final Object arg0 = args[0];
if (arg0 instanceof Runnable) {
args[0] = new Runnable() {
@Override
public void run() {
final Runnable task = (Runnable) arg0;
try {
task.run();
if (task instanceof Future<?>) {
final Future<?> future = (Future<?>) task;
if (future.isDone()) {
try {
future.get();
} catch (final CancellationException ce) {
// Your error-handling code here
ce.printStackTrace();
} catch (final ExecutionException ee) {
// Your error-handling code here
ee.getCause().printStackTrace();
} catch (final InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
} catch (final RuntimeException re) {
// Your error-handling code here
re.printStackTrace();
throw re;
} catch (final Error e) {
// Your error-handling code here
e.printStackTrace();
throw e;
}
}
};
} else if (arg0 instanceof Callable<?>) {
args[0] = new Callable<Object>() {
@Override
public Object call() throws Exception {
final Callable<?> task = (Callable<?>) arg0;
try {
return task.call();
} catch (final Exception e) {
// Your error-handling code here
e.printStackTrace();
throw e;
} catch (final Error e) {
// Your error-handling code here
e.printStackTrace();
throw e;
}
}
};
}
}
return method.invoke(executor, args);
});
}
這是因為AbstractExecutorService :: submit
將您的runnable
包裝到RunnableFuture
(只有FutureTask
)中,如下所示
AbstractExecutorService.java
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null); /////////HERE////////
execute(ftask);
return ftask;
}
然后execute
將它傳遞給Worker
並且Worker.run()
將調用下面的。
ThreadPoolExecutor.java
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); /////////HERE////////
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
最后
task.run();
在上面的代碼中調用將調用FutureTask.run()
。 這是異常處理程序代碼,因此您沒有得到預期的異常。
class FutureTask<V> implements RunnableFuture<V>
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) { /////////HERE////////
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
這類似於mmm的解決方案,但更容易理解。 讓您的任務擴展一個包含 run() 方法的抽象類。
public abstract Task implements Runnable {
public abstract void execute();
public void run() {
try {
execute();
} catch (Throwable t) {
// handle it
}
}
}
public MySampleTask extends Task {
public void execute() {
// heavy, error-prone code here
}
}
醫生的例子沒有給我想要的結果。
當 Thread 進程被放棄時(使用顯式interput();
s)出現異常。
此外,我想保留普通主線程具有的典型throw
的“System.exit”功能,我想要這樣,這樣程序員就不會被迫處理代碼而不得不擔心它的上下文(......一個線程),如果出現任何錯誤,它必須是編程錯誤,或者必須通過手動捕獲來解決問題......真的不需要過於復雜。
所以我更改了代碼以滿足我的需求。
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
Future<?> future = (Future<?>) r;
boolean terminate = false;
try {
future.get();
} catch (ExecutionException e) {
terminate = true;
e.printStackTrace();
} catch (InterruptedException | CancellationException ie) {// ignore/reset
Thread.currentThread().interrupt();
} finally {
if (terminate) System.exit(0);
}
}
}
不過要小心,這段代碼基本上將你的線程轉換成一個主線程異常明智,同時保持它的所有並行屬性......但讓我們成為現實,根據系統的並行機制( extends Thread
)設計架構是錯誤的方法恕我直言......除非嚴格要求事件驅動設計......但是......如果這是要求,問題是:在這種情況下甚至需要 ExecutorService 嗎?......也許不需要。
而不是子類化 ThreadPoolExecutor,我會為它提供一個ThreadFactory實例,該實例創建新線程並為它們提供UncaughtExceptionHandler
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.