如何从线程捕获异常

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


}
}
}

有人知道为什么吗?

232204 次浏览

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

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

如果你想知道这些未捕获的异常,你可以试试这个:

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

有关未捕获异常处理的更多信息可以找到在这里

使用Callable而不是Thread,然后你可以调用Future#get(),它会抛出Callable抛出的任何异常。

请看线程。UncaughtExceptionHandler

更好的(替代)方法是使用可调用的未来来获得相同的结果…

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

你可能会在那里找到问题的答案……好运!: -)

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

线程和异常处理

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

扩展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。

最有可能的;

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

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

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

如果你实现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)

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

如果我们不设置uncaughtExceptionHandler,那么线程线程组就会充当一个处理程序。

 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给出了很好的解释

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

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

更倾向于高级java.util.concurrent API用于多线程,如ExecutorServiceThreadPoolExecutor

你可以自定义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>);

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

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

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

@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被触发,它将循环遍历列表,停止每个线程并重新启动主函数重新创建所有线程。