[英]How to catch an Exception from a thread
我有 Java main class,在 class 中,我啟動了一個新線程,在 main 中,它一直等到線程死亡。 某個時刻,我從線程拋出一個運行時異常,但是在main class中無法捕捉到線程拋出的異常。
這是代碼:
public class Test extends Thread
{
public static void main(String[] args) throws InterruptedException
{
Test t = new Test();
try
{
t.start();
t.join();
}
catch(RuntimeException e)
{
System.out.println("** RuntimeException from main");
}
System.out.println("Main stoped");
}
@Override
public void run()
{
try
{
while(true)
{
System.out.println("** Started");
sleep(2000);
throw new RuntimeException("exception from thread");
}
}
catch (RuntimeException e)
{
System.out.println("** RuntimeException from thread");
throw e;
}
catch (InterruptedException e)
{
}
}
}
有人知道為什么嗎?
使用Thread.UncaughtExceptionHandler
。
Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread th, Throwable ex) {
System.out.println("Uncaught exception: " + ex);
}
};
Thread t = new Thread() {
@Override
public void run() {
System.out.println("Sleeping ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted.");
}
System.out.println("Throwing exception ...");
throw new RuntimeException();
}
};
t.setUncaughtExceptionHandler(h);
t.start();
這是因為異常是線程本地的,而您的主線程實際上並沒有看到run
方法。 我建議您閱讀有關線程如何工作的更多信息,但快速總結一下:您的啟動調用start
了一個不同的線程,與您的主線程完全無關。 join
呼叫只是等待它完成。 在線程中拋出並且從未捕獲的異常會終止它,這就是join
在您的主線程上返回的原因,但異常本身會丟失。
如果您想了解這些未捕獲的異常,可以嘗試以下操作:
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Caught " + e);
}
});
有關未捕獲異常處理的更多信息,請參見此處。
這解釋了線程的 state 轉換取決於是否發生異常:
資料來源: http://www-public.imtbs-tsp.eu/~gibson/Teaching/CSC7322/L8-ExceptionsAndThreads.pdf
最有可能的;
但是,假設您確實需要處理來自另一個子線程的異常。 我會使用這樣的 ExecutorService:
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Void> future = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
System.out.println("** Started");
Thread.sleep(2000);
throw new IllegalStateException("exception from thread");
}
});
try {
future.get(); // raises ExecutionException for any uncaught exception in child
} catch (ExecutionException e) {
System.out.println("** RuntimeException from thread ");
e.getCause().printStackTrace(System.out);
}
executor.shutdown();
System.out.println("** Main stopped");
印刷
** Started
** RuntimeException from thread
java.lang.IllegalStateException: exception from thread
at Main$1.call(Main.java:11)
at Main$1.call(Main.java:6)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
** Main stopped
使用Callable
而不是 Thread,然后您可以調用Future#get()
,它會拋出 Callable 拋出的任何異常。
目前您只捕獲RuntimeException
,它是Exception
的子 class 。 但是您的應用程序可能會拋出Exception的其他子類。 除了RuntimeException
之外,還捕獲通用Exception
由於線程方面的許多內容都已更改,因此請使用高級 java API。
對於像ExecutorService
或ThreadPoolExecutor
這樣的多線程,更喜歡提前 java.util.concurrent API 。
您可以自定義ThreadPoolExecutor來處理異常。
oracle 文檔頁面的示例:
覆蓋
protected void afterExecute(Runnable r,
Throwable t)
在完成給定 Runnable 的執行時調用的方法。 此方法由執行任務的線程調用。 如果非 null,則 Throwable 是導致執行突然終止的未捕獲的 RuntimeException 或 Error。
示例代碼:
class ExtendedExecutor extends ThreadPoolExecutor {
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}
用法:
ExtendedExecutor service = new ExtendedExecutor();
我在上面的代碼之上添加了一個構造函數:
public ExtendedExecutor() {
super(1,5,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
}
您可以更改此構造函數以滿足您對線程數的要求。
ExtendedExecutor service = new ExtendedExecutor();
service.submit(<your Callable or Runnable implementation>);
我遇到了同樣的問題......幾乎沒有解決方法(僅用於實現而不是匿名對象)......我們可以將 class 級別異常 object 聲明為 null......然后在運行方法的 catch 塊中對其進行初始化......運行方法出錯,這個變量不會是 null .. 然后我們可以讓 null 檢查這個特定的變量,如果它不是 null,那么線程執行中有異常。
class TestClass implements Runnable{
private Exception ex;
@Override
public void run() {
try{
//business code
}catch(Exception e){
ex=e;
}
}
public void checkForException() throws Exception {
if (ex!= null) {
throw ex;
}
}
}
在 join() 之后調用 checkForException()
同樣從 Java 8 您可以將 Dan Cruz 的答案寫為:
Thread t = new Thread(()->{
System.out.println("Sleeping ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted.");
}
System.out.println("Throwing exception ...");
throw new RuntimeException(); });
t.setUncaughtExceptionHandler((th, ex)-> log(String.format("Exception in thread %d id: %s", th.getId(), ex)));
t.start();
AtomicReference 也是一種將錯誤傳遞給主線程的解決方案。與 Dan Cruz 的方法相同。
AtomicReference<Throwable> errorReference = new AtomicReference<>();
Thread thread = new Thread() {
public void run() {
throw new RuntimeException("TEST EXCEPTION");
}
};
thread.setUncaughtExceptionHandler((th, ex) -> {
errorReference.set(ex);
});
thread.start();
thread.join();
Throwable newThreadError= errorReference.get();
if (newThreadError!= null) {
throw newThreadError;
}
唯一的變化是,您可以使用 AtomicReference 代替創建 volatile 變量,它在幕后做了同樣的事情。
如果在啟動線程的 class 中實現 Thread.UncaughtExceptionHandler ,則可以設置然后重新拋出異常:
public final class ThreadStarter implements Thread.UncaughtExceptionHandler{
private volatile Throwable initException;
public void doSomeInit(){
Thread t = new Thread(){
@Override
public void run() {
throw new RuntimeException("UNCAUGHT");
}
};
t.setUncaughtExceptionHandler(this);
t.start();
t.join();
if (initException != null){
throw new RuntimeException(initException);
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
initException = e;
}
}
這會導致以下 output:
Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: UNCAUGHT
at com.gs.gss.ccsp.enrichments.ThreadStarter.doSomeInit(ThreadStarter.java:24)
at com.gs.gss.ccsp.enrichments.ThreadStarter.main(ThreadStarter.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.RuntimeException: UNCAUGHT
at com.gs.gss.ccsp.enrichments.ThreadStarter$1.run(ThreadStarter.java:15)
Thread 中的異常處理:默認情況下,run() 方法不會拋出任何異常,因此 run 方法中的所有已檢查異常都必須在此處捕獲和處理,對於運行時異常,我們可以使用 UncaughtExceptionHandler。 UncaughtExceptionHandler 是 Java 提供的一個接口,用於處理 Thread run 方法中的異常。 所以我們可以實現這個接口並使用 setUncaughtExceptionHandler() 方法將我們實現的 class 設置回線程 object。 但是這個處理程序必須在我們調用踏板上的 start() 之前設置。
如果我們不設置 uncaughtExceptionHandler 則 Threads ThreadGroup 充當處理程序。
public class FirstThread extends Thread {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("FirstThread doing something urgent, count : "
+ (count++));
throw new RuntimeException();
}
}
public static void main(String[] args) {
FirstThread t1 = new FirstThread();
t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
System.out.printf("Exception thrown by %s with id : %d",
t.getName(), t.getId());
System.out.println("\n"+e.getClass());
}
});
t1.start();
}
}
你玩過 setDefaultUncaughtExceptionHandler() 和線程 class 的類似方法嗎? 來自 API:“通過設置默認的未捕獲異常處理程序,應用程序可以更改那些已經接受任何“默認”行為的線程處理未捕獲異常的方式(例如記錄到特定設備或文件)提供的系統。”
您可能會在那里找到問題的答案...祝您好運:-)
我使用 RxJava 的解決方案:
@Test(expectedExceptions = TestException.class)
public void testGetNonexistentEntry() throws Exception
{
// using this to work around the limitation where the errors in onError (in subscribe method)
// cannot be thrown out to the main thread
AtomicReference<Exception> ex = new AtomicReference<>();
URI id = getRandomUri();
canonicalMedia.setId(id);
client.get(id.toString())
.subscribe(
m ->
fail("Should not be successful"),
e ->
ex.set(new TestException()));
for(int i = 0; i < 5; ++i)
{
if(ex.get() != null)
throw ex.get();
else
Thread.sleep(1000);
}
Assert.fail("Cannot find the exception to throw.");
}
對於那些需要停止所有線程運行並在其中任何一個因異常停止時重新運行所有線程的人:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// could be any function
getStockHistory();
}
public void getStockHistory() {
// fill a list of symbol to be scrapped
List<String> symbolListNYSE = stockEntityRepository
.findByExchangeShortNameOnlySymbol(ContextRefreshExecutor.NYSE);
storeSymbolList(symbolListNYSE, ContextRefreshExecutor.NYSE);
}
private void storeSymbolList(List<String> symbolList, String exchange) {
int total = symbolList.size();
// I create a list of Thread
List<Thread> listThread = new ArrayList<Thread>();
// For each 1000 element of my scrapping ticker list I create a new Thread
for (int i = 0; i <= total; i += 1000) {
int l = i;
Thread t1 = new Thread() {
public void run() {
// just a service that store in DB my ticker list
storingService.getAndStoreStockPrice(symbolList, l, 1000,
MULTIPLE_STOCK_FILL, exchange);
}
};
Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable exception) {
// stop thread if still running
thread.interrupt();
// go over every thread running and stop every one of them
listThread.stream().forEach(tread -> tread.interrupt());
// relaunch all the Thread via the main function
getStockHistory();
}
};
t1.start();
t1.setUncaughtExceptionHandler(h);
listThread.add(t1);
}
}
總結一下:
您有一個主要的 function 創建多個線程,每個線程都有 UncaughtExceptionHandler 由線程內的任何異常觸發。 您將每個線程添加到列表中。 如果觸發了 UncaughtExceptionHandler,它將遍歷列表,停止每個線程並重新啟動主 function 重新創建所有線程。
擴展Thread
幾乎總是錯誤的。 我不能 state 這足夠強大。
Thread
是錯誤的。* 如果您改為實現Runnable
,您將看到預期的行為。
public class Test implements Runnable {
public static void main(String[] args) {
Test t = new Test();
try {
new Thread(t).start();
} catch (RuntimeException e) {
System.out.println("** RuntimeException from main");
}
System.out.println("Main stoped");
}
@Override
public void run() {
try {
while (true) {
System.out.println("** Started");
Thread.sleep(2000);
throw new RuntimeException("exception from thread");
}
} catch (RuntimeException e) {
System.out.println("** RuntimeException from thread");
throw e;
} catch (InterruptedException e) {
}
}
}
生產;
Main stoped
** Started
** RuntimeException from threadException in thread "Thread-0" java.lang.RuntimeException: exception from thread
at Test.run(Test.java:23)
at java.lang.Thread.run(Thread.java:619)
* 除非您想更改應用程序使用線程的方式,在 99.9% 的情況下您不會這樣做。 如果您認為自己屬於 0.1% 的案例,請參閱規則 #1。
你不能這樣做,因為它沒有真正的意義。 如果您沒有調用t.join()
,那么當t
線程拋出異常時,您的主線程可能位於代碼中的任何位置。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.