如何从线程中捕获异常


165

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

    }
  }
}

有人知道为什么吗?

Answers:


220

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

13
如果我想将异常上层抛出该怎么办?
rodi 2015年

6
@rodi将ex保存到上级可以在处理程序中看到的volatile变量(例如,成员变量)。在外面,检查是否为null,否则抛出。或使用新的volatile字段扩展UEH并将异常存储在此处。
西罗Santilli郝海东冠状病六四事件法轮功

1
我想从线程内部捕获异常-不停止它。这会以某种方式有用吗?
Lealo

42

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

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

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

有关未捕获的异常处理的更多信息,请参见此处


我喜欢!使用静态方法设置处理程序Thread.setDefaultUncaughtExceptionHandler()还会捕获线程“ main”中的异常
Teo


23

最有可能的;

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

但是,假设您确实需要处理另一个子线程的异常。我会这样使用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()在线程完成执行之前,是否不等待或阻塞?
Gregor Valentin

@GregorValentin等待/阻塞,直到线程完成Runnable / Callable。
彼得·劳瑞


3

使用Callable而不是Thread,然后可以进行调用Future#get(),该方法引发Callable引发的任何异常。


1
请注意,抛出的异常Callable.call包装在内ExcecutionException,必须对其原因进行评估。
Karl Richter

3

目前,您仅捕获RuntimeException的子类Exception。但是您的应用程序可能会抛出Exception的其他子类。Exception除了捕获通用RuntimeException

由于在Threading方面已经做了很多更改,因此请使用高级Java API。

对于多线程(例如或),最好使用高级java.util.concurrent APIExecutorServiceThreadPoolExecutor

您可以自定义ThreadPoolExecutor以处理异常。

oracle文档页面中的示例:

覆写

protected void afterExecute(Runnable r,
                            Throwable t)

给定Runnable执行完成时调用的方法。该方法由执行任务的线程调用。如果不为空,则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>);

2

我遇到了同样的问题...很少解决(仅用于实现而不是匿名对象)...我们可以将类级别的异常对象声明为null ...然后在运行方法的catch块中对其进行初始化...如果存在是run方法中的错误,此变量将不会为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()


1

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

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


1

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

1

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变量,该变量在后台进行了相同的操作。


0

扩展几乎总是错误的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。


7
这不会在main方法中捕获异常。
philwb 2011年

强烈建议不要扩展Thread类。我读了这篇文章,并解释了为什么要在OJPC准备中进行解释。本书...猜猜,他们知道他们在说什么
luigi7up 2012年

2
“ RuntimeException from main”从未在此打印
。.main中

0

如果您在启动Threads的类中实现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()将同步。
NickL 2015年

0

线程中的异常处理:默认情况下,run()方法不会引发任何异常,因此,必须仅在其中捕获和处理run方法中所有已检查的异常,对于运行时异常,我们可以使用UncaughtExceptionHandler。UncaughtExceptionHandler是Java提供的接口,用于处理Thread run方法中的异常。因此,我们可以实现此接口,并使用setUncaughtExceptionHandler()方法将实现类放回Thread对象。但是必须在我们在踏板上调用start()之前设置此处理程序。

如果我们未设置uncaughtExceptionHandler,则线程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中给出了很好的解释


0

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

0

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

@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,它由线程内部的任何Exception触发。您将每个线程添加到列表。如果触发了UncaughtExceptionHandler,它将循环遍历List,停止每个线程,然后重新启动主要功能,重新创建所有线程。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.