【问题标题】:How to catch an Exception from a thread如何从线程中捕获异常
【发布时间】:2011-09-26 15:13:12
【问题描述】:

我有 Java 主类,在类中,我启动了一个新线程,在主类中,它一直等到线程死亡。在某个时刻,我从线程中抛出了运行时异常,但我无法在主类中捕获从线程中抛出的异常。

代码如下:

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)
    {

    }
  }
}

有人知道为什么吗?

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    使用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();
    

    【讨论】:

    • 如果我想将异常抛出到上层该怎么办?
    • @rodi 将 ex 保存到上层可以在处理程序中看到的 volatile 变量(例如成员变量)。在外面,检查是否为空,否则抛出。或者使用新的 volatile 字段扩展 UEH 并将异常存储在那里。
    • 我想从我的线程内部捕获一个异常 - 不停止它。这会有什么用吗?
    • @M4rk, Re, “...我想将异常抛出到上层。”没有未捕获的异常处理程序可以抛出的“上层”。不能跨线程边界抛出异常(请参阅下面 abyx 的答案),并且未捕获的异常处理程序本身抛出的任何异常都将被忽略。在这种情况下,您可以做的最好的事情是让处理程序将未捕获的异常对象放入队列中,并让主线程定期检查队列以找出是否引发了异常。
    【解决方案2】:

    对于那些需要停止所有线程运行并重新运行所有线程,当其中任何一个线程因异常而停止时:

    @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);
    
        }
    
    }
    

    总结一下:

    您有一个创建多个线程的主函数,每个线程都有 UncaughtExceptionHandler 由线程内的任何异常触发。您将每个线程添加到列表中。如果触发了 UncaughtExceptionHandler,它将遍历 List,停止每个 Thread 并重新启动 main 函数重新创建所有 Thread。

    【讨论】:

      【解决方案3】:

      这解释了线程的状态转换取决于是否发生异常:

      来源:http://www-public.imtbs-tsp.eu/~gibson/Teaching/CSC7322/L8-ExceptionsAndThreads.pdf

      【讨论】:

      • 您是否创建了图表?如果不是,来源是什么?
      【解决方案4】:

      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 变量,它在幕后做了同样的事情。

      【讨论】:

      • 如果你要加入一个新线程来调用一个线程,你为什么还要使用它呢?你的例子根本没有意义。您只需创建一个线程然后阻塞直到它完成,这与根本不使用线程相同。
      【解决方案5】:

      这是因为异常是线程本地的,而您的主线程实际上并没有看到 run 方法。我建议您阅读有关线程如何工作的更多信息,但要快速总结一下:您对start 的调用启动了一个不同的线程,与您的主线程完全无关。对join 的调用只是等待它完成。在线程中抛出并且从未被捕获的异常会终止它,这就是为什么join 在您的主线程上返回,但异常本身会丢失。

      如果您想了解这些未捕获的异常,可以尝试以下操作:

      Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
          @Override
          public void uncaughtException(Thread t, Throwable e) {
              System.out.println("Caught " + e);
          }
      });
      

      更多关于未捕获异常处理的信息可以在here找到。

      【讨论】:

      • 我喜欢这样!使用静态方法Thread.setDefaultUncaughtExceptionHandler() 设置处理程序也会捕获线程“main”中的异常
      【解决方案6】:

      从 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();
      

      【讨论】:

        【解决方案7】:

        我遇到了同样的问题...很少有解决方法(仅用于实现而不是匿名对象)...我们可以将类级别的异常对象声明为 null ...然后在运行方法的 catch 块中对其进行初始化.. . 如果 run 方法有错误,这个变量不会为空 .. 然后我们可以对这个特定的变量进行空检查,如果它不为空,那么线程执行中有异常。

        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()

        【讨论】:

          【解决方案8】:

          我使用 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.");
          }
          

          【讨论】:

            【解决方案9】:

            请看Thread.UncaughtExceptionHandler

            更好的(替代)方法是使用CallableFuture 来获得相同的结果...

            【讨论】:

              【解决方案10】:

              目前您只捕获RuntimeExceptionException 的子类。但是您的应用程序可能会抛出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>);
              

              【讨论】:

                【解决方案11】:

                很有可能;

                • 您无需将异常从一个线程传递到另一个线程。
                • 如果您想处理异常,只需在引发异常的线程中执行即可。
                • 在本例中,您的主线程不需要等待后台线程,这实际上意味着您根本不需要后台线程。

                但是,假设您确实需要处理来自另一个子线程的异常。我会使用这样的 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
                

                【讨论】:

                • 但是future.get()不会等待或阻塞直到线程完成执行吗?
                • @GregorValentin 它等待/阻塞,直到线程完成 Runnable/Callable。
                【解决方案12】:

                Thread 中的异常处理:默认情况下,run() 方法不会抛出任何异常,因此 run 方法中的所有已检查异常都必须在此处捕获和处理,对于运行时异常,我们可以使用 UncaughtExceptionHandler。 UncaughtExceptionHandler 是 Java 提供的一个接口,用于处理 Thread run 方法中的异常。所以我们可以实现这个接口,并使用 setUncaughtExceptionHandler() 方法将我们的实现类设置回 Thread 对象。但是这个处理程序必须在我们调用踏板上的 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给出了很好的解释

                【讨论】:

                  【解决方案13】:

                  如果你在启动线程的类中实现了 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;
                      }    
                  
                  }
                  

                  导致以下输出:

                  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)
                  

                  【讨论】:

                  • 无需使 Throwable initException 变得易失,因为 t.join() 将同步。
                  • 这是一个很棒的方法。
                  【解决方案14】:

                  扩展Thread 几乎总是错误的。我不能说得足够强烈。

                  多线程规则 #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。

                  【讨论】:

                  • 这不会在main方法中捕获异常。
                  • 强烈建议不要扩展 Thread 类。我在 OJPC 准备中阅读了此内容并解释了原因。书...猜猜,他们知道他们在说什么
                  • "RuntimeException from main" 永远不会在这里打印.. 异常没有在 main 中捕获
                  【解决方案15】:

                  你玩过 setDefaultUncaughtExceptionHandler() 和 Thread 类的类似方法吗?来自 API:“通过设置默认的未捕获异常处理程序,应用程序可以更改那些已经接受任何“默认”行为的线程处理未捕获异常的方式(例如记录到特定设备或文件)系统提供。”

                  您可能会在那里找到问题的答案...祝您好运! :-)

                  【讨论】:

                    【解决方案16】:

                    使用Callable 代替Thread,然后您可以调用Future#get(),它会抛出Callable 抛出的任何异常。

                    【讨论】:

                    • 请注意,Callable.call 中抛出的异常被包裹在 ExcecutionException 中,并且必须评估其原因。
                    【解决方案17】:

                    你不能这样做,因为它没有真正的意义。如果您没有调用t.join(),那么当t 线程抛出异常时,您的主线程可能位于代码中的任何位置。

                    【讨论】:

                      猜你喜欢
                      • 2010-12-27
                      • 2021-03-05
                      • 1970-01-01
                      • 2012-11-04
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多