【并发编程】java多线程 未捕获异常 分析与处理【全网孤本】 持续更新补全中

全网孤本可能是在吹🍇… 争取全网最全

下次更新可能单独开篇

  • UncaughtExceptionHandler 原理分析
  • CompletableFuture 异常处理


本文主要整理了工作中常见的 线程异常处理方法,涵盖了对于手动创建的线程执行任务与线程池执行任务的异常处理等,除此之外还有关于FutureTask、CompletableFuture等执行时的异常处理的问题分析。

Thread提供的 未捕获异常处理

UncaughtExceptionHandler 分析

线程未捕获异常处理流程图

在这里插入图片描述

java多线程异常处理的流程图

源码分析

在这里插入图片描述

在这里插入图片描述
这里源码说起来就有点多了 TODO …

方式一:Thread 对象的 setUncaughtExceptionHandler

    public static void test01() {
        Thread ttt = new Thread(() -> {
            ThreadUtils.sleepSec(3);
            int a = 10 / 0;
            System.out.println("a = " + a);
        }, "TTT");

        ttt.setUncaughtExceptionHandler((t, e) -> {
            System.out.println("[t:" + t + "]" + "\n[e:" + e + "]");
        });

        ttt.start();
    }

执行结果

[TTT] INFO com.chengnanwuyin.thread_exception.ExceptionTest_01 - 
===== Thread:TTT-e:java.lang.ArithmeticException: / by zero =====

方式二:重写ThreadGroup的uncaughtException

与方法一本质相同

    private static void test02() {
        ThreadGroup threadGroup = new ThreadGroup("G-1") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                log.error("\n===== [测试线程内异常抓取-group-uncaught] ERROR-Thread:{}-e:{} =====",
                        t.getName(), e.getMessage());
            }
        };

        Thread ttt02 = new Thread(threadGroup, () -> {
            ThreadUtils.sleepSec(1);
            int a = 10 / 0;
        }, "TTT-02");

        ttt02.start();
    }

执行结果

[TTT-02] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_02_Group - 
===== [测试线程内异常抓取-group-uncaught] ERROR-Thread:TTT-02-e:/ by zero =====

方法三:设置默认的异常处理器

实现方式

  • 继承 Thread.UncaughtExceptionHandler
  • 重写 uncaughtException方法
    注意事项
  • 这里是为Thread设置其生命周期内的默认异常处理器,对某一个线程使用setUncaughtExceptionHandler 设置的异常处理,优先级高于默认异常处理。
/**
 * @author SurfingMaster
 * @date 2023/6/12
 */
public class ExceptionTest {
    private static final Logger log = LoggerFactory.getLogger(ExceptionTest_03.class);

    public static void main(String[] args) {

        Thread.setDefaultUncaughtExceptionHandler(new DefaultExceptionHandler());

        Thread ttt01 = new Thread(() -> {
            int a = 10 / 0;
        }, "TTT-01");

        ttt01.setUncaughtExceptionHandler((t, e) -> {
            log.error("\n===== [测试线程内异常抓取-setUncaughtExceptionHandler] Thread:{}-e:{} =====", currentThread().getName(), e.getMessage());
        });


        Thread ttt02 = new Thread(() -> {
            Integer[] integers = new Integer[-1];
        }, "TTT-02");

        ttt01.start();
        ttt02.start();

    }


    /**
     * 自定义异常处理:
     * 继承 Thread.UncaughtExceptionHandler <br>
     * 重写 uncaughtException方法 <br>
     */
    public static class DefaultExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            log.error("\n===== [测试线程内异常抓取-DefaultExceptionHandler-uncaughtException] DefaultExceptionHandler-Thread:{}-e:{} =====",
                    currentThread().getName(), e.getMessage());
        }
    }
}

执行结果

 [TTT-02] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_03 - 
===== [测试线程内异常抓取-DefaultExceptionHandler-uncaughtException] DefaultExceptionHandler-Thread:TTT-02-e:null =====
 [TTT-01] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_03 - 
===== [测试线程内异常抓取-setUncaughtExceptionHandler] Thread:TTT-01-e:/ by zero =====

线程池的异常处理

  • 利用 UncaughtExceptionHandler 处理 线程池的异常和处理手动创建线程的异常在本质上一样的
  • 利用线程池的方法处理(例如afterExecute),这里和UncaughtExceptionHandler的本质就有了区别。

方法一:线程内异常处理

并无特点,线程内的异常 线程内处理。

    public static void case01_try_catch() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 2000, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());

        AtomicReference<Thread> thAtomicRef = new AtomicReference<Thread>();

        executor.execute(() -> {
            try {
                Thread value = currentThread();
                thAtomicRef.set(value);
                System.out.println(4 / 0);
            } catch (Exception e) {
                //log.error("\n===== 线程内部 ERROR-Thread:{}-e:{} =====", JSONObject.toJSONString(currentThread()), e.getMessage());
                log.error("\n===== 线程内部 ERROR-Thread:{}-e:{} =====", currentThread().getName() + currentThread().getState(), e.getMessage());
            }
        });

        ThreadUtils.sleepSec(1);
        log.info("\n===== Thread:{}-state:{} =====", thAtomicRef.get().getName(), thAtomicRef.get().getState());
    }

执行结果

 [pool-1-thread-1] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== 线程内部 ERROR-Thread:pool-1-thread-1RUNNABLE-e:/ by zero =====
 [main] INFO com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== Thread:pool-1-thread-1-state:WAITING =====

执行分析

🧐,扒开看看其中线程池中的对象,最值得关注的是 UncaughtExceptionHandler
在这里插入图片描述
在这里插入图片描述
如果能给这里的UncaughtExceptionHandler赋值,那么异常处理会更加游刃有余。

方法二:自定义ThreadFactory,指定给线程池

基于线程池创建的对象的原理,关键是要清楚线程池对象的线程哪里来的。观察上一个方法的线程对象,自然有了此方法。

	/**
     * 
     * 继承实现ThreadFactory <br>
     * 重写 setUncaughtExceptionHandler
     * @param
     * @return void
     * @author SurfingMaster
     */
    private static void case_factoryUncaughtException() {
        AtomicReference<Thread> thAtomicRef = new AtomicReference<Thread>();

        ThreadPoolExecutor thPoolExe = new ThreadPoolExecutor(1, 2, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName("special-Thread-");
                        thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {
                            log.error("\n===== Thread:{}-t:{}-e:{} =====", currentThread().getName(), JSONObject.toJSONString(t), e);
                        });
                        return thread;
                    }
                },
                new ThreadPoolExecutor.AbortPolicy());

        thPoolExe.execute(() -> {
            thAtomicRef.set(currentThread());
            log.info("\n===== Thread:{} =====", currentThread().getName());
            int a = 10 / 0;
        });

        ThreadUtils.sleepSec(5);

        log.info("\n===== ExceptionTest_05_execute.main-thread.getState():{} =====", thAtomicRef.get().getState());
    }
  • 这个方法对应于 手动创建线程的异常处理 的方法一:单独为线程设置 setUncaughtExceptionHandler

执行结果

 [special-Thread-] INFO com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== Thread:special-Thread- =====
 [special-Thread-] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== Thread:special-Thread--t:RUNNABLE-e:/ by zero =====

执行分析

创建线程池的时候,使用了自定义的线程工厂:
在这里插入图片描述
线程池执行任务时,线程的异常处理对象是创建时的对象
在这里插入图片描述
既然都 debug 到这里了,看看线程池执行的时候,怎么调用的异常处理方法。
在这里插入图片描述
debug追踪到这里,面对afterExecute,显然有了重写afterExecute的冲动。

方法三:重写 ThreadGroup的 异常处理函数

这个方法对应于,处理手动创建线程异常的方法二。

    /**
     * 重写 ThreadGroup的 异常处理函数
     *
     * @author SurfingMaster
     */
    private static void case_threadFactoryGroup() {
    
        AtomicReference<Thread> thAtomicRef = new AtomicReference<Thread>();

        ThreadPoolExecutor thExe = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(new TTT_ThreadGroup("G1"), r);
                thread.setName("G1-T-");
                return thread;
            }
        }, new ThreadPoolExecutor.AbortPolicy());

        thExe.execute(() -> {
            Thread curTh = currentThread();
            thAtomicRef.set(curTh);
            log.info("\n===== Thread:{} =====", currentThread().getName());
            int a = 10 / 0;
        });

        ThreadUtils.sleepSec(5);

        log.info("\n===== ExceptionTest_05_execute.main-thread.getState():{} =====", thAtomicRef.get().getState());

    }
	/**
     * 继承ThreadGroup <br>
     * 重写uncaughtException
     */
    public static class TTT_ThreadGroup extends ThreadGroup {
        public TTT_ThreadGroup(String name) {
            super(name);
        }

		// 这里也可以为当前组指定 上级
        public TTT_ThreadGroup(ThreadGroup parent, String name) {
            super(parent, name);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            super.uncaughtException(t, e);
            log.info("\n===== Thread:{}-t:{}-e:{} =====", currentThread().getName(), t, e);
        }
    }

执行结果

 [G1-T-] INFO com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== Thread:G1-T- =====
 [G1-T-] INFO com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== Thread:G1-T--t:G1-T-RUNNABLE-e:{} =====
java.lang.ArithmeticException: / by zero
	at com.chengnanwuyin.thread_exception.ExceptionTest_05_execute.lambda$case_threadFactoryGroup$0(ExceptionTest_05_execute.java:71)
	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:750)
Exception in thread "G1-T-" java.lang.ArithmeticException: / by zero
	at com.chengnanwuyin.thread_exception.ExceptionTest_05_execute.lambda$case_threadFactoryGroup$0(ExceptionTest_05_execute.java:71)
	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:750)
 [main] INFO com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== ExceptionTest_05_execute.main-thread.getState():TERMINATED =====

这里的执行过程与上一个方法本质相同。

方法五: 重写 afterExecute 方法

这个方法其实是比较常用的方法了。
先看一眼大神Doug Lea 给的源码和注释。
在这里插入图片描述

  • 其中值得注意的是关于Runnable的描述:the runnable that has completed。也就是说,这里任务已经执行结束了。这里相当于是异步的

这里在创建 ThreadPoolExecutor对象的时候直接重写,也可以为某一类任务定义子类。当然生产上这里是要规范地写在配置类里。

    /**
     * 继承  ThreadPoolExecutor 重写 afterExecute 方法 <br>
     *
     * @author SurfingMaster
     */
    public static void case_afterExecute() {
        AtomicReference<Thread> thAtomicRef = new AtomicReference<Thread>();

        ThreadPoolExecutor thPoolExe = new ThreadPoolExecutor(1, 2, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy()) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                log.error("\n===== 执行异常 ERROR-r:{}-t:{} =====", JSONObject.toJSONString(r), t.getMessage());
            }
        };

        thPoolExe.execute(() -> {
            thAtomicRef.set(currentThread());
            log.info("\n===== Thread:{} =====", currentThread().getName());
            int a = 10 / 0;
        });
        ThreadUtils.sleepSec(1);

        log.info("\n===== thread.getState():{} =====", thAtomicRef.get().getState());

    }

执行结果

 [pool-1-thread-1] INFO com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== Thread:pool-1-thread-1 =====
 [pool-1-thread-1] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== 执行异常 ERROR-r:{}-t:/ by zero =====
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.chengnanwuyin.thread_exception.ExceptionTest_05_execute.lambda$case_afterExecute$2(ExceptionTest_05_execute.java:190)
	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:750)
 [main] INFO com.chengnanwuyin.thread_exception.ExceptionTest_05_execute - 
===== thread.getState():TERMINATED =====

执行分析

在这里插入图片描述
观察线程与任务的标识。
在这里插入图片描述

这里调用 afterExecute 方法传入的 Runnable 本质 仍为 其实现(Thread)的对象,并且标识与上一步的一致。
在这里插入图片描述

方法六:

这不算什么方法,就是针对 Future这种任务get方法的异常处理。

    /**
     * @author SurfingMaster
     */
    public static void case_futrue() {
        AtomicReference<Thread> thRef = new AtomicReference<>();

        ThreadPoolExecutor thExe = new ThreadPoolExecutor(2, 3, 3, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                new ThreadPoolExecutor.AbortPolicy());

        Future<Integer> future = thExe.submit(() -> {
            thRef.set(currentThread());
            int a = 10 / 0;
            return a;
        });

        Integer res = null;
        try {
            res = future.get();
        } catch (InterruptedException | ExecutionException e) {
            log.error("\n===== [线程池执行submit-get处理异常] ERROR-e:{} =====", e.getMessage());
        }

        log.info("\n===== [线程池执行submit-get处理异常] -res:{}-COMPLETED. =====", res);

        log.info("\n===== Thread:{}-thRef:{} =====", JSONObject.toJSONString(thRef.get()), thRef);

    }

执行结果

 [main] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_06_submit - 
===== [线程池执行submit-get处理异常] ERROR-e:java.lang.ArithmeticException: / by zero =====
 [main] INFO com.chengnanwuyin.thread_exception.ExceptionTest_06_submit - 
===== [线程池执行submit-get处理异常] -res:null-COMPLETED. =====

番外篇

对以上方法的补充

特殊关注一下FutureTask的方法

像方法五直接使用 afterExecute 方法,是抓取不到异常的;显然,FutureTask 任务,使用 setUncaughtExceptionHandler 也不会起作用。

这里大哥李的注释已经说的非常清楚了。在这里插入图片描述

简而言之,线程池对象执行 task.run() 时,它自己维护执行的异常,task.run调用时,捕获不到FutureTask的异常,所以需要"further probe":通过get方法显式调用然后抓取😏。

但是这样和在外部获取get时处理异常好像并没有什么优势。emmm,除了使用了FutureTask但不调用get…如果没有这个兜底,异常可能就丢了。

这里是执行分析图:

【并发编程】FutureTask 执行 / 异常捕获 分析图

先上代码,再分析。

    /**
     * @author SurfingMaster
     */
    public static void case_futrue() {
        AtomicReference<Thread> thRef = new AtomicReference<>();

        ThreadPoolExecutor thExe = new ThreadPoolExecutor(2, 3, 3, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                new ThreadPoolExecutor.AbortPolicy()) {

            @Override
            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) {
                    log.error("\n===== 执行异常-afterExecute处理-r:{}-t:{} =====", JSONObject.toJSONString(r), t.getMessage());
                }
            }
        };

        Future<Integer> future = thExe.submit(() -> {
            thRef.set(currentThread());
            int a = 10 / 0;
            return a;
        });

        Integer res = null;
        try {
            ThreadUtils.sleepSec(1);
            res = future.get();
        } catch (InterruptedException | ExecutionException e) {
            log.error("\n===== [线程池执行submit-外部get处理异常] ERROR-e:{} =====", e.getMessage());
        }

        //log.info("\n===== Thread:{}-thRef:{} =====", JSONObject.toJSONString(thRef.get()), thRef);

    }

执行结果

 [pool-1-thread-1] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_06_submit - 
===== 执行异常-afterExecute处理-r:{"cancelled":false,"done":true}-t:/ by zero =====
 [main] ERROR com.chengnanwuyin.thread_exception.ExceptionTest_06_submit - 
===== [线程池执行submit-外部get处理异常] ERROR-e:java.lang.ArithmeticException: / by zero =====

执行分析

在这里插入图片描述
至于捕获不到的原因,就得聊 FutureTask 的执行:

  • 先用一句话概括:遇到异常时 try-catch 了异常,并且保存了下来,在get时才会抛出。
  • FutureTask 封装的比较严密,其返回值存储在 outcome 中,嗯大哥李很骚,用 state、stateOffset 来标记 outcome 的类型,最后在get中调用report方法。

【并发编程】FutureTask 执行 / 异常捕获 分析图

CompletableFuture 异常处理

TODO…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值