• 线程池执行的用户任务抛出异常会怎样


    线程池执行的用户任务抛出异常会怎样

    ThreadPoolExecutor.execute

    源码分析

    看源码可以知道,ThreadPoolExecutor中的任务都是在runWorker中执行的
    在这里插入图片描述
    通过源码可以看到

    • 1149行执行用户任务
    • 1150~1155处理捕获任务异常,并抛出
    • 抛出异常后会退出,从任务队列中拉取任务的循环
    • 然后执行1167行,worker线程退出的逻辑

    看一下线程退出的逻辑
    在这里插入图片描述

    • 如果是异常退出,参数completedAbruptly为true
    • 如果状态值比STOP小,即线程池没有停止,会重新创建一个worker线程

    总结

    ThreadPoolExecutor.execute 如果用户任务抛出了异常,在线程池运行的状态下,会重新创建一个worker线程。
    这里就可能存在一个风险,即如果用户任务大量的抛出异常,可能会导出线程资源频繁的销毁、创建。
    因此,需要用户任务应当主动对异常进行处理,而不是消极的抛给线程池。

    ThreadPoolExecutor.submit

    源码分析

    通过 ThreadPoolExecutor.submit 提交的用户任务,会包装成一个FutureTask,返回一个Future对象。因此异常处理的逻辑和ThreadPoolExecutor.execute有些差别

    看一个FutureTask.run方法
    在这里插入图片描述
    从源码可以看到,FutureTask执行用户任务,如果异常,不会对外抛出,仅是记录

    但是在调用Future.get时,会抛出异常,但此时的线程不是线程池的线程了,而是用户线程,因此对线程池是友好的。

    总结

    ThreadPoolExecutor.submit 提交的用户任务,会包装成一个FutureTask,FutureTask执行用户任务,如果异常,不会对外抛出,仅是记录,但是在调用Future.get时,会抛出异常。
    异常是在用户线程中抛出的,因此不影响线程池中的线程。

    ScheduledThreadPoolExecutor.schedule

    源码分析

    ScheduledThreadPoolExecutor.schedule会将用户任务包装为ScheduledFutureTask,ScheduledFutureTask是FutureTask的子类,看下ScheduledFutureTask的执行逻辑
    在这里插入图片描述

    ScheduledFutureTask是FutureTask的子类,所以有异常时,也不是抛出,而是记录

    • 293行对于非周期性任务,执行一次,如果有异常,不抛出,仅记录
    • 294~296对于周期性任务,执行完本次后,会设置下次执行的时间。如果本次出现异常,ScheduledFutureTask.super.runAndReset()返回结果为false(这个可以从源码看到),此时不会设置下次任务调度的时间了,因此会导致周期性任务失效的现象,并且异常信息不会抛出,也不会打印

    总结

    ScheduledThreadPoolExecutor.schedule提交的用户任务,如果出现异常,是不会抛出的,也不会打印。
    对于非周期性任务和周期性任务,执行异常,都没有办法感知(不抛出、看不到)。
    对于周期性任务,任何一次调度时,任务出现异常,都会导致后续无法调度。
    因此,在使用这个线程池时,我们应当保证用户任务不会抛出异常到执行线程,避免任务调度失效,和异常排查困难等问题。

    思考:ThreadPoolExecutor.execute发生异常时为什么要退出

    ThreadPoolExecutor.execute出现用户任务异常时,为什么要退出当前线程,再重新创建一个线程呢?
    我思考了半天,觉得原因之一可能是:
    ThreadPoolExecutor的可以指定线程工厂,如果我的线程工厂是这样的

        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        // 处理异常的逻辑
                    }
                });
                return thread;
            }
        };
    即在ThreadFactory创建线程时,指定了对未捕获的异常的处理器。
    这种情况下,如果线程池不把发生异常的线程退出,可能会导致异常没有走到用户期望的逻辑上,因此需要将发生异常的线程退出,然后JVM调用UncaughtExceptionHandler
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    插件收集(idea Communtity Edtion)
    任务交给谁?委派模式告诉你最佳选择!
    Django(二、静态文件的配置、链接数据库MySQL)
    转码服务serverless探索
    分页查询和聚合查询
    SpringBoot+Layui+shiro 前后端分离项目
    OC-范畴
    第四次作业
    【11.11】Codeforces 刷题
    Python自然语言处理的力量:NLTK库介绍
  • 原文地址:https://blog.csdn.net/qq_26824159/article/details/126767372