簡體   English   中英

如何從線程中捕獲異常

[英]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

請看一下Thread.UncaughtExceptionHandler

更好的(替代)方法是使用CallableFuture來獲得相同的結果......

使用Callable而不是 Thread,然后您可以調用Future#get() ,它會拋出 Callable 拋出的任何異常。

目前您只捕獲RuntimeException ,它是Exception的子 class 。 但是您的應用程序可能會拋出Exception的其他子類。 除了RuntimeException之外,還捕獲通用Exception

由於線程方面的許多內容都已更改,因此請使用高級 java API。

對於像ExecutorServiceThreadPoolExecutor這樣的多線程,更喜歡提前 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();
}
}

http://coder2design.com/thread-creation/#exceptions給出了很好的解釋

你玩過 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 這足夠強大。

多線程規則 #1:擴展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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM