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

代码如下:

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几乎总是错误的。我怎么强调都不为过。

多线程规则1:扩展线程是错误的

如果您实现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。

其他回答

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

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

我也遇到过同样的问题……很少的工作(只用于实现而不是匿名对象)…我们可以将类级异常对象声明为null…然后在catch块for run方法中初始化它…如果在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()

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

目前您只捕获RuntimeException, Exception的一个子类。但是您的应用程序可能会抛出Exception的其他子类。捕获除RuntimeException之外的泛型异常

由于线程前端已经改变了许多东西,请使用高级java API。

优先选择高级java.util.concurrent API用于多线程,如ExecutorService或ThreadPoolExecutor。

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

示例来自oracle文档页:

覆盖

protected void afterExecute(Runnable r,
                            Throwable t)

方法,在完成给定可运行对象的执行时调用。此方法由执行任务的线程调用。如果非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>);