线程池吃了你的异常?

线程池吃了你的异常?

下面主要从线程池的两个提交任务的方法角度讲一下这个问题。

ExecutorService 的 submit 和 execute 两个方法的区别

先来看两段使用 submit 的代码
代码 1
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class FinallyTest {
    private static Object str = null;

    public static void main(String[] args) {
        TestRun testRun = new TestRun();
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<?> future = executor.submit(testRun);
        try {
            //future.get(5, TimeUnit.SECONDS);
        }catch (Throwable t){
          	System.out.println("run 方法的异常被 主线程 捕获了。");
            t.printStackTrace();
        }
        System.out.println("main方法结束");
    }

    public static class TestRun implements Runnable {

        public void run() {
            System.out.println(str.toString());
        }
    }
}

运行结果:

main方法结束
代码 2
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class FinallyTest {
    private static Object str = null;

    public static void main(String[] args) {
        TestRun testRun = new TestRun();
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<?> future = executor.submit(testRun);
        try {
            future.get(5, TimeUnit.SECONDS);
        }catch (Throwable t){
          	System.out.println("run 方法的异常被 主线程 捕获了。");
            t.printStackTrace();
        }
        System.out.println("main方法结束");
    }

    public static class TestRun implements Runnable {

        public void run() {
            System.out.println(str.toString());
        }
    }
}

运行结果:

run 方法的异常被 主线程 捕获了。
java.util.concurrent.ExecutionException: java.lang.NullPointerException
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:206)
	at com.zph.study.base.jvm.FinallyTest.main(FinallyTest.java:16)
Caused by: java.lang.NullPointerException
	at com.zph.study.base.jvm.FinallyTest$TestRun.run(FinallyTest.java:27)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
main方法结束
原因是什么呢?

因为调用 submit 之后,run 方法抛出的异常被线程池捕获并封装到了 Future 的 get 方法上。代码 1 并没有调用 Future.get ,这样异常只是被捕获了,并没有进一步抛出来,就像被线程池吃掉了一样。而 代码 2 调用了 Future.get ,get 方法将 run 方法抛出的异常进一步抛给了调用 get 方法的地方,所以这里就能够打印出 run 方法的异常信息了。

再看两段使用 submit 的代码
代码 3
import lombok.SneakyThrows;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class FinallyTest {
    private static Object str = null;

    public static void main(String[] args) {
        TestRun testRun = new TestRun();
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<?> future = executor.submit(testRun);
        try {
            //future.get(5, TimeUnit.SECONDS);
        }catch (Throwable t){
            System.out.println("run 方法的异常被 主线程 捕获了。");
            t.printStackTrace();
        }
        System.out.println("main方法结束");
    }

    public static class TestRun implements Runnable {

        @SneakyThrows
        public void run() {
            try {
                System.out.println(str.toString());
            }catch (Exception e){
                System.out.println("run 方法的 catch 输出。");
                e.printStackTrace();
                System.out.println("run 方法结束,并抛出异常");
                throw e;
            }
        }
    }
}

运行结果:

main方法结束
run 方法的 catch 输出。
java.lang.NullPointerException
	at com.zph.study.base.jvm.FinallyTest$TestRun.run(FinallyTest.java:30)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
run 方法结束,并抛出异常
代码 4
import lombok.SneakyThrows;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class FinallyTest {
    private static Object str = null;

    public static void main(String[] args) {
        TestRun testRun = new TestRun();
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<?> future = executor.submit(testRun);
        try {
            future.get(5, TimeUnit.SECONDS);
        }catch (Throwable t){
            System.out.println("run 方法的异常被 主线程 捕获了。");
            t.printStackTrace();
        }
        System.out.println("main方法结束");
    }

    public static class TestRun implements Runnable {

        @SneakyThrows
        public void run() {
            try {
                System.out.println(str.toString());
            }catch (Exception e){
                System.out.println("run 方法的 catch 输出。");
                e.printStackTrace();
                System.out.println("run 方法结束,并抛出异常");
                throw e;
            }
        }
    }
}

运行结果:

run 方法的 catch 输出。
java.lang.NullPointerException
	at com.zph.study.base.jvm.FinallyTest$TestRun.run(FinallyTest.java:30)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
run 方法结束,并抛出异常
run 方法的异常被 主线程 捕获了。
java.util.concurrent.ExecutionException: java.lang.NullPointerException
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:206)
	at com.zph.study.base.jvm.FinallyTest.main(FinallyTest.java:17)
Caused by: java.lang.NullPointerException
	at com.zph.study.base.jvm.FinallyTest$TestRun.run(FinallyTest.java:30)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
main方法结束
原因是什么呢?

代码 3 的运行结果我们可以看到,当 run 方法发生异常时,主线程已经结束了。而 代码 4 的运行结果则恰好相反,run 方法结束并抛出异常之后,主线程才捕获到异常,然后主线程结束。这就说明了 Future.get 会阻塞调用者的线程,直到通过 submit 提交的任务的 run 方法执行结束之后才会放开调用 Future.get 的线程。这也就是说,Future.get 将两个并行执行的线程变成了顺序执行。所以与其说线程池是将 run 方法抛出的异常捕获并封装到了 Future 的 get 方法上。倒不如说是调用了 Future.get 之后,调用者线程就一直在等待 run 方法执行完毕,如果 run 方法抛出了异常,就经由 Future.get 方法传递给调用者。

再来看 2 段使用 execute 的代码
代码 5
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FinallyTest {
    private static Object str = null;

    public static void main(String[] args) {
        TestRun testRun = new TestRun();
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(testRun);
        try {//这个 try 代码块显得很无用,但我放在这里只是想和下边的代码做一个对照
        }catch (Throwable t){
            System.out.println("run 方法的异常被 主线程 捕获了。");
            t.printStackTrace();
        }
        System.out.println("main方法结束");
    }

    public static class TestRun implements Runnable {

        public void run() {
            System.out.println(str.toString());
        }
    }
}

运行结果:

main方法结束
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
	at com.zph.study.base.jvm.FinallyTest$TestRun.run(FinallyTest.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
代码6
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FinallyTest {
    private static Object str = null;

    public static void main(String[] args) {
        TestRun testRun = new TestRun();
        ExecutorService executor = Executors.newCachedThreadPool();
        try {
            executor.execute(testRun);
        }catch (Throwable t){
            System.out.println("run 方法的异常被 主线程 捕获了。");
            t.printStackTrace();
        }
        System.out.println("main方法结束");
    }

    public static class TestRun implements Runnable {

        public void run() {
            System.out.println(str.toString());
        }
    }
}

运行结果:

main方法结束
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
	at com.zph.study.base.jvm.FinallyTest$TestRun.run(FinallyTest.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
原因是什么呢?

可以看到这两段代码的执行结果完全一样,即使把 execute 的调用放在 try 块中,run 方法抛出的异常也不会被这里的 catch 块捕获到。原因是调用 execute 方法时,线程池并不会对 run 方法抛出的异常进行捕获和处理,异常直接被抛给了 jvm ,jvm 就自己打印出来了。而且放入线程池中执行的 run 方法是异步执行的,很可能在主线程结束的时候,run 方法才刚刚抛出异常,这就更不可能被主线程捕获了。

总结
submit 和 execute 的区别

ExecutorService 的 submit 和 execute 两个方法刚好互补。

  • submit 是无法自动打印异常,因为它将 run 方法抛出的异常捕获后封装到了 Future 的 get 方法上

    在主线程中调用 Future.get 就能捕获到 run 方法中抛出的异常

    • Future.get 会阻塞调用者的线程,直到通过 submit 提交的任务的 run 方法执行结束之后才会放开调用 Future.get 的线程。这也就是说,Future.get 将两个并行执行的线程变成了顺序执行。所以与其说线程池是将 run 方法抛出的异常捕获并封装到了 Future 的 get 方法上。倒不如说是调用了 Future.get 之后,调用者线程就一直在等待 run 方法执行完毕,如果 run 方法抛出了异常,就经由 Future.get 方法传递给调用者。
    • 如果不调用 Future.get 的话,run 方法抛出的异常就像被线程池吃掉了一样,有时候会让你摸不着头脑。明明 run方法抛出异常了,而你的控制台却没有任何异常打印,原因就出在了这里。
    public interface Future<V> {
        /**
         * 
         * @throws CancellationException if the computation was cancelled
         * @throws ExecutionException if the computation threw an
         * exception
         * @throws InterruptedException if the current thread was interrupted
         * while waiting
         * @throws TimeoutException if the wait timed out
         */
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }
    
  • execute 恰好相反,它在 run 方法抛出异常后直接交给 jvm 打印了,由于 run 方法是异步方法,所以在

    主线程中就无法捕获到 run 方法抛出的异常了

本人属于菜鸟一个,如果有哪些地方写的不对的,希望各位大佬予以指正,谢谢了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AiIsAlive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值