public class SimpleTest {
@Test
public void threadThrowException() {
Thread thread = new Thread(()->{int a = 1/0;});
thread.start();
}
@Test
public void threadThrowException2() {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
int a = 1 / 0;
return a;
});
Thread thread = new Thread(futureTask);
thread.start();
}
@Test
public void threadPoolThrowException() throws ExecutionException, InterruptedException {
ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2);
Future<?> future = threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
int a = 1/0;
}
});
}
@Test
public void threadPoolThrowException2() throws ExecutionException, InterruptedException {
ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2);
Future<?> future = threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
int a = 1/0;
}
});
future.get();
}
}
- 很明显,上面的几个都会抛异常,但异常信息都会打印出来吗?跑一遍发现,只有1和4会打印出异常堆栈,2和3并不会打印出来。
- 在我们执行thread.start时发生了什么呢,会调用到native方法start0。jvm会通过folk之类的方法去os申请资源,然后调用runnable的run方法。
- 线程什么时候结束呢,当方法执行完毕或者异常退出的时候,线程会结束。
- 首先,我们来看jvm是如何处理异常的。
- 当程序中发生异常时,发生异常的method会创建一个exception的object,包含了一场的类型,堆栈信息等,然后传递给jvm。这个过程叫做throw exception。 jvm在接受到异常信息之后,开始在堆栈中匹配是否有合适的exception handle来处理这个异常,如果没找到合适的,则交给jvm的一个default exception handle。
- 而在Thread中,我们可以看到,线程在我们未设置exceptionhandle的时候,默认有一个exceptionhandle
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
- 可以看到默认处理办法就是打印异常的堆栈信息
- 那既然这样,我们也可以手动为Thread设置exceptionhandler。
@Test
public void threadThrowException() {
Thread thread = new Thread(()->{int a = 1/0;});
thread.setUncaughtExceptionHandler((t,e)->{
System.out.println("my exception handle:");
e.printStackTrace();
});
thread.start();
}
- 那为啥扔到线程池或者硬futureTask修饰之后,就不抛异常了呢。
- 扔到线程池其实也就是用futureTask修饰了下,那看下futureTask是咋执行run方法的.
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
- 可以看到,在run方法的外部,是catch住了所有的throwable,那run方法发生的异常都会被这里捕获到,不会轮到defaultExceptionHandler来执行。因此不会打出异常堆栈,而当我们调用future.get的时候,最终会抛出一个由ExecutionException包裹的exception。
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
- 这里就有个坑就是,如果直接往线程池里扔任务执行,在不掉用get的情况下,任务抛异常是看不到的
ref