线程抛异常之后发生了什么

  • 先看几个case
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

posted @ 2021-07-18 15:33  Nooooone  阅读(97)  评论(0)    收藏  举报