简体   繁体   English

如何从线程中捕获异常

[英]How to catch an Exception from a thread

I have Java main class, in the class, I start a new thread, in the main, it waits until the thread dies.我有 Java main class,在 class 中,我启动了一个新线程,在 main 中,它一直等到线程死亡。 At some moment, I throw a runtime exception from the thread, but I can't catch the exception thrown from the thread in the main class.某个时刻,我从线程抛出一个运行时异常,但是在main class中无法捕捉到线程抛出的异常。

Here is the code:这是代码:

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

    }
  }
}

Anybody knows why?有人知道为什么吗?

Use a Thread.UncaughtExceptionHandler .使用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();

That's because exceptions are local to a thread, and your main thread doesn't actually see the run method.这是因为异常是线程本地的,而您的主线程实际上并没有看到run方法。 I suggest you read more about how threading works, but to quickly summarize: your call to start starts up a different thread, totally unrelated to your main thread.我建议您阅读有关线程如何工作的更多信息,但快速总结一下:您的启动调用start了一个不同的线程,与您的主线程完全无关。 The call to join simply waits for it to be done. join呼叫只是等待它完成。 An exception that is thrown in a thread and never caught terminates it, which is why join returns on your main thread, but the exception itself is lost.在线程中抛出并且从未捕获的异常会终止它,这就是join在您的主线程上返回的原因,但异常本身会丢失。

If you want to be aware of these uncaught exceptions you can try this:如果您想了解这些未捕获的异常,可以尝试以下操作:

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

More information about uncaught exception handling can be found here .有关未捕获异常处理的更多信息,请参见此处

This explains the state transition of threads dependening on whether an exceptions occured or not:这解释了线程的 state 转换取决于是否发生异常:

线程和异常处理

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

Most likely;最有可能的;

  • you don't need to pass the exception from one thread to another.您不需要将异常从一个线程传递到另一个线程。
  • if you want to handle an exception, just do it in the thread which threw it.如果要处理异常,只需在抛出异常的线程中执行即可。
  • your main thread doesn't need to wait from the background thread in this example, which actually means you don't need a background thread at all.在此示例中,您的主线程不需要从后台线程等待,这实际上意味着您根本不需要后台线程。

However, lets assume you do need to handle an exception from a child thread another.但是,假设您确实需要处理来自另一个子线程的异常。 I would use an ExecutorService like this:我会使用这样的 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");

prints印刷

** 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

Please take a look at Thread.UncaughtExceptionHandler请看一下Thread.UncaughtExceptionHandler

Better (alternative) way is to use Callable and Future to get the same result...更好的(替代)方法是使用CallableFuture来获得相同的结果......

Use Callable instead of Thread, then you can call Future#get() which throws any exception that the Callable threw.使用Callable而不是 Thread,然后您可以调用Future#get() ,它会抛出 Callable 抛出的任何异常。

Currently you are catching only RuntimeException , a sub class of Exception .目前您只捕获RuntimeException ,它是Exception的子 class 。 But your application may throw other sub-classes of Exception .但是您的应用程序可能会抛出Exception的其他子类。 Catch generic Exception in addition to RuntimeException除了RuntimeException之外,还捕获通用Exception

Since many of things have been changed on Threading front, use advanced java API.由于线程方面的许多内容都已更改,因此请使用高级 java API。

Prefer advance java.util.concurrent API for multi-threading like ExecutorService or ThreadPoolExecutor .对于像ExecutorServiceThreadPoolExecutor这样的多线程,更喜欢提前 java.util.concurrent API

You can customize your ThreadPoolExecutor to handle exceptions.您可以自定义ThreadPoolExecutor来处理异常。

Example from oracle documentation page: oracle 文档页面的示例:

Override覆盖

protected void afterExecute(Runnable r,
                            Throwable t)

Method invoked upon completion of execution of the given Runnable.在完成给定 Runnable 的执行时调用的方法。 This method is invoked by the thread that executed the task.此方法由执行任务的线程调用。 If non-null, the Throwable is the uncaught RuntimeException or Error that caused execution to terminate abruptly.如果非 null,则 Throwable 是导致执行突然终止的未捕获的 RuntimeException 或 Error。

Example code:示例代码:

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

Usage:用法:

ExtendedExecutor service = new ExtendedExecutor();

I have added one constructor on top of above code as:我在上面的代码之上添加了一个构造函数:

 public ExtendedExecutor() { 
       super(1,5,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
   }

You can change this constructor to suit your requirement on number of threads.您可以更改此构造函数以满足您对线程数的要求。

ExtendedExecutor service = new ExtendedExecutor();
service.submit(<your Callable or Runnable implementation>);

I faced the same issue... little work around (only for implementation not anonymous objects )... we can declare the class level exception object as null... then initialize it inside the catch block for run method... if there was error in run method,this variable wont be null.. we can then have null check for this particular variable and if its not null then there was exception inside the thread execution.我遇到了同样的问题......几乎没有解决方法(仅用于实现而不是匿名对象)......我们可以将 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;
            }
        }
}     

call checkForException() after join()在 join() 之后调用 checkForException()

Also from Java 8 you can write Dan Cruz answer as:同样从 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 is also a solution to pass the error to the main thread.Is same approach like the one of Dan Cruz. 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;
    }  

The only change is that instead of creating a volatile variable you can use AtomicReference which did same thing behind the scenes.唯一的变化是,您可以使用 AtomicReference 代替创建 volatile 变量,它在幕后做了同样的事情。

If you implement Thread.UncaughtExceptionHandler in class that starts the Threads, you can set and then rethrow the exception:如果在启动线程的 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;
    }    

}

Which causes the following output:这会导致以下 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)

Exception handling in Thread: By default run() method doesn't throw any exception, so all checked exceptions inside the run method has to be caught and handled there only and for runtime exceptions we can use UncaughtExceptionHandler. Thread 中的异常处理:默认情况下,run() 方法不会抛出任何异常,因此 run 方法中的所有已检查异常都必须在此处捕获和处理,对于运行时异常,我们可以使用 UncaughtExceptionHandler。 UncaughtExceptionHandler is an interface provided by Java to handle exceptions in a Thread run method. UncaughtExceptionHandler 是 Java 提供的一个接口,用于处理 Thread run 方法中的异常。 So we can implement this interface and set back our implementing class back to Thread object using setUncaughtExceptionHandler() method.所以我们可以实现这个接口并使用 setUncaughtExceptionHandler() 方法将我们实现的 class 设置回线程 object。 But this handler has to be set before we call start() on the tread.但是这个处理程序必须在我们调用踏板上的 start() 之前设置。

if we don't set uncaughtExceptionHandler then the Threads ThreadGroup acts as a handler.如果我们不设置 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();
}
}

Nice explanation given at http://coder2design.com/thread-creation/#exceptions http://coder2design.com/thread-creation/#exceptions给出了很好的解释

Did you play around with setDefaultUncaughtExceptionHandler() and the alike methods of the Thread class?你玩过 setDefaultUncaughtExceptionHandler() 和线程 class 的类似方法吗? From the API: "By setting the default uncaught exception handler, an application can change the way in which uncaught exceptions are handled (such as logging to a specific device, or file) for those threads that would already accept whatever "default" behavior the system provided."来自 API:“通过设置默认的未捕获异常处理程序,应用程序可以更改那些已经接受任何“默认”行为的线程处理未捕获异常的方式(例如记录到特定设备或文件)提供的系统。”

You might find the answer to your problem there... good luck: :-)您可能会在那里找到问题的答案...祝您好运:-)

My solution with RxJava:我使用 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.");
}

For those who needs to stop all Threads running and re-run all of them when any one of them is stopped on an Exception:对于那些需要停止所有线程运行并在其中任何一个因异常停止时重新运行所有线程的人:

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

    }

}

To sum up:总结一下:

You have a main function that create multiple thread, each of them has UncaughtExceptionHandler which is trigger by any Exception inside of a thread.您有一个主要的 function 创建多个线程,每个线程都有 UncaughtExceptionHandler 由线程内的任何异常触发。 You add every Thread to a List.您将每个线程添加到列表中。 If a UncaughtExceptionHandler is trigger it will loop through the List, stop every Thread and relaunch the main function recreation all the Thread.如果触发了 UncaughtExceptionHandler,它将遍历列表,停止每个线程并重新启动主 function 重新创建所有线程。

It is almost always wrong to extend Thread .扩展Thread几乎总是错误的。 I cannot state this strongly enough.我不能 state 这足够强大。

Multithreading Rule #1: Extending Thread is wrong.*多线程规则 #1:扩展Thread是错误的。*

If you implement Runnable instead you will see your expected behaviour.如果您改为实现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) {

    }
  }
}

produces;生产;

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)

* unless you want to change the way your application uses threads, which in 99.9% of cases you don't. * 除非您想更改应用程序使用线程的方式,在 99.9% 的情况下您不会这样做。 If you think you are in the 0.1% of cases, please see rule #1.如果您认为自己属于 0.1% 的案例,请参阅规则 #1。

You cannot do this, since it doesn't really make sense.你不能这样做,因为它没有真正的意义。 If you hadn't called t.join() then you main thread could be anywhere in the code when the t thread throws an exception.如果您没有调用t.join() ,那么当t线程抛出异常时,您的主线程可能位于代码中的任何位置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM