Java线程池异常处理机制

前言

@Test
    void ThreadTest() {


        System.out.println("aaaaaaaaaaa");
        Thread t = new Thread(() -> System.out.println(1 / 0));

        t.start();
        System.out.println("bbbbbbbbbbbbbbbbb");


    }

如果我们执行上面这段代码,会在控制台上看到异常输出。 在Java中,线程中的异常是不能抛出到调用该线程的外部方法中捕获的,只能在线程内部进行捕获。

为什么不能抛出异常到外部线程捕获?

因为线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。基于这样的设计理念,在Java中,线程方法的异常都应该在线程代码边界之内(run方法内)进行处理。换句话说,我们不能捕获从线程中逃逸的异常。

JDK如何控制线程异常不会跑到外部线程?

通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束。

如果在线程中抛出异常线程会怎么样?

线程会立即终结。

aaaaaaaaaaa
bbbbbbbbbbbbbbbbb
Exception in thread "Thread-544" java.lang.ArithmeticException: / by zero
	at 

通常情况下绝大多数线上应用不会将控制台作为日志输出地址,而是另有日志输出。这种情况下,上面的代码所抛出异常便会丢失。那为了将异常输出到日志中,我们会这样写代码:

@Test
    void ThreadTest() {


        System.out.println("aaaaaaaaaaa");
        Thread t = new Thread(() -> {
            try{


                System.out.println(1 / 0);


            }catch (Exception e){

                e.printStackTrace();
                log.error(e.getMessage());
            }
        }






        );

        t.start();
        System.out.println("bbbbbbbbbbbbbbbbb");


    }
aaaaaaaaaaa
bbbbbbbbbbbbbbbbb
2022-04-13 16:19:43.338 ERROR 15984 --- [     Thread-544] ERROR  : / by zero

这样我们就能异常栈输出到日志中,而不是控制台,从而避免异常的丢失。可能好多线程任务默认的异常处理机制都是相同的。比如都是将异常输出到日志文件。按照上面的写法会造成重复代码。那我们该如何解决这个问题呢?

Java线程–全局异常处理

接口 UncaughtExceptionHandler

Thread 类中有个接口 UncaughtExceptionHandler,
使用全局异常处理 UncaughtExceptionHandler
UncaughtExceptionHandler能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器(这是Thread类中的接口):

public interface UncaughtExceptionHanlder {
void uncaughtException(Thread t, Throwable e);
}
JDK5之后可以在每一个Thread对象上添加一个异常处理器UncaughtExceptionHandler 。Thread.UncaughtExceptionHandler.uncaughtException()方法会在线程因未捕获的异常而面临死亡时被调用。

我们通过 Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler) 设置全局的默认异常处理机制。


@Test
    void ThreadTest() {


        System.out.println("aaaaaaaaaaa");
        Thread t = new Thread(() -> {

                System.out.println(1 / 0);

                System.out.println("ccccccccccccc");

        }






        );

        t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
        t.start();
        System.out.println("bbbbbbbbbbbbbbbbb");


    }
class MyUncheckedExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("捕获到异常。异常栈信息为:"+e.getMessage());
        e.printStackTrace();
    }
}

线程组实现

@Test
    void ThreadTest() {

        System.out.println("aaaaaaaaaaa");
        ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                log.error("捕获到异常。异常栈信息为:"+e.getMessage());
                e.printStackTrace();
            }
        };

        Thread t = new Thread(threadGroup, () -> {

                System.out.println(1 / 0);

                System.out.println("ccccccccccccc");

        }

        );

       // t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
        t.start();
        System.out.println("bbbbbbbbbbbbbbbbb");


    }

aaaaaaaaaaa
bbbbbbbbbbbbbbbbb
2022-04-13 16:45:27.482 ERROR 87200 — [ Thread-544] ERROR Test : 捕获到异常。异常栈信息为:/ by zero
java.lang.ArithmeticException: / by zero
at

默认的handler

如果只需要一个线程异常处理器处理线程的异常,那么我们可以设置一个默认的线程异常处理器,当线程出现异常时,
如果没有指定线程的异常处理器,而且线程组也没有设置,那么就会使用默认的线程异常处理器

@Test
    void ThreadTest() {

        System.out.println("aaaaaaaaaaa");
       /* ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                log.error("捕获到异常。异常栈信息为:"+e.getMessage());
                e.printStackTrace();
            }
        };*/

        //Thread t = new Thread(threadGroup, () -> {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
        Thread t = new Thread(() -> {

                System.out.println(1 / 0);

                System.out.println("ccccccccccccc");

        }

        );

       // t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
        t.start();
        System.out.println("bbbbbbbbbbbbbbbbb");


    }

ThreadPoolExecutor处理异常

(1)在提交的任务中将异常捕获并处理,不抛给线程池。

(2)异常抛给线程池,但是我们要及时处理抛出的异常。

简单代码例子

@Test
    void ThreadTest() {

        System.out.println("aaaaaaaaaaa");
       /* ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                log.error("捕获到异常。异常栈信息为:"+e.getMessage());
                e.printStackTrace();
            }
        };*/

        //Thread t = new Thread(threadGroup, () -> {
        /*Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
        Thread t = new Thread(() -> {

                System.out.println(1 / 0);

                System.out.println("ccccccccccccc");

        }

        );

       // t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
        t.start();
        System.out.println("bbbbbbbbbbbbbbbbb");*/

        ThreadPoolExecutor executor =  new ThreadPoolExecutor(10, 21, 3000, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());


        log.info("开始提交任务");
        executor.execute(DwckzbServiceTest::doSomeThing);
        log.info("提交任务完成");


    }

    private static void doSomeThing() {
        int value = 5 / 0;
    }

结果
aaaaaaaaaaa
2022-04-13 17:06:05.927 INFO 56408 — [ main]: 开始提交任务
2022-04-13 17:06:05.928 INFO 56408 — [ main] 提交任务完成
Exception in thread “pool-6-thread-1” java.lang.ArithmeticException: / by zero

java.lang.IllegalStateException: Shutdown in progress
at java.lang.ApplicationShutdownHooks.remove(ApplicationShutdownHooks.java:82)
at java.lang.Runtime.removeShutdownHook(Runtime.java:239)
at org.apache.rocketmq.client.trace.AsyncTraceDispatcher.removeShutdownHook(AsyncTraceDispatcher.java:210)

线程池提交任务有两种方式,一种是 execute 方式,另一种是 submit 方式。

execute 方式

1、直接catch

第一种思路很简单,就是我们提交任务的时候,将所有可能的异常都Catch住,并且自己处理。

@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {

    @Test
    void saveBatchTest() {

        ThreadPoolExecutor executor = poolExecutor(1, 1);
        log.info("开始提交任务");
        executor.execute(LiuTest::doSomeThing);
        log.info("提交任务完成");

    }

    public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
    }

    private static ReturnModel doSomeThing() {

        int result ;
        try {
            int value = 10 / 0;
            result = 2;
        } catch (Exception e) {
            log.error("doSomeThing execute Exception: ", e);
            return ReturnModel.newFailureInstance();
        }
        return ReturnModel.newSuccessInstance(result);
    }
}

说白了就是把业务逻辑都trycatch起来。 但是这种思路的缺点就是:

所有的不同任务类型都要trycatch,增加了代码量。

不存在checkedexception的地方也需要都trycatch起来,代码丑陋。

自定义线程池,继承ThreadPoolExecutor并复写其afterExecute(Runnable r, Throwable t)方法。

@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {

    @Test
    void liuTest() {

        ThreadPoolExecutor executor = poolExecutor(1, 1);
        log.info("开始提交任务");
        executor.execute(LiuTest::doSomeThing);
        log.info("提交任务完成");

    }

    /*public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
    }*/
    public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new CustomThreadPoolExecutor(1,2,60,TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
    }

    private static ReturnModel doSomeThing() {

        int result ;
       /* try {
            int value = 10 / 0;
            result = 2;
        } catch (Exception e) {
            log.error("doSomeThing execute Exception: ", e);
            return ReturnModel.newFailureInstance();
        }*/
        int value = 10 / 0;
        result = 2;
        return ReturnModel.newSuccessInstance(result);
    }


    static class CustomThreadPoolExecutor extends ThreadPoolExecutor {

        public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                        BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
        }

        @Override
        protected void afterExecute(Runnable r, Throwable e) {
            super.afterExecute(r, e);
            log.error(e.getMessage());
            log.error("CustomThreadPoolExecutor execute Exception: "+
                    ThreadUtils.stackTrace(e.getStackTrace()));
        }
    }


    static class ThreadUtils {


        public static String stackTrace(StackTraceElement[] stackTrace) {
            if (stackTrace != null && stackTrace.length > 0) {
                StringBuilder logs = new StringBuilder(512);
                for (StackTraceElement e : stackTrace) {
                    logs.append(java.text.MessageFormat.format("{0}: {1}(): {2}" , e.getClassName(), e.getMethodName(),
                            e.getLineNumber())).append("\n");
                }
                return logs.toString();
            }
            return "";
        }
    }

}

运行结果

2022-04-14 09:55:49.651  INFO 7916 --- [           main] INFO  LiuTest : 开始提交任务
2022-04-14 09:55:49.652  INFO 7916 --- [           main] INFO  .LiuTest : 提交任务完成
2022-04-14 09:55:49.652 ERROR 7916 --- [pool-6-thread-1] ERROR LiuTest : / by zero
2022-04-14 09:55:49.654 ERROR 7916 --- [pool-6-thread-1] ERROR LiuTest : CustomThreadPoolExecutor execute Exception: LiuTest: doSomeThing(): 44
java.util.concurrent.ThreadPoolExecutor: runWorker(): 1,142
java.util.concurrent.ThreadPoolExecutor$Worker: run(): 617
java.lang.Thread: run(): 745

实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e);方法,并将该handler传递给线程池的ThreadFactory

@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {

    @Test
    void liuTest() {

        ThreadPoolExecutor executor = poolExecutor(1, 1);
        log.info("开始提交任务");
        executor.execute(LiuTest::doSomeThing);
        log.info("提交任务完成");

    }

    /*public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
    }*/
    /*public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), r -> new Thread(new CustomThreadGroup("CustomThreadGroup"), r),
                new ThreadPoolExecutor.AbortPolicy());
    }*/

    public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), r -> {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler((t, e) ->
                    log.error("CustomThreadPoolExecutor execute Exception: {}",
                            ThreadUtils.stackTrace(e.getStackTrace())));
            return thread;
        }, new ThreadPoolExecutor.AbortPolicy());
    }


    static class CustomThreadGroup extends ThreadGroup {

        public CustomThreadGroup(String name) {
            super(name);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            super.uncaughtException(t, e);
            log.error(e.getMessage());
            log.error("CustomThreadPoolExecutor execute Exception: {}",
                    ThreadUtils.stackTrace(e.getStackTrace()));
        }
    }

    private static ReturnModel doSomeThing() {

        int result ;
       /* try {
            int value = 10 / 0;
            result = 2;
        } catch (Exception e) {
            log.error("doSomeThing execute Exception: ", e);
            return ReturnModel.newFailureInstance();
        }*/
        int value = 10 / 0;
        result = 2;
        return ReturnModel.newSuccessInstance(result);
    }


   /* static class CustomThreadPoolExecutor extends ThreadPoolExecutor {

        public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                        BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
        }

        @Override
        protected void afterExecute(Runnable r, Throwable e) {
            super.afterExecute(r, e);
            log.error(e.getMessage());
            log.error("CustomThreadPoolExecutor execute Exception: "+
                    ThreadUtils.stackTrace(e.getStackTrace()));
        }
    }*/


    static class ThreadUtils {


        public static String stackTrace(StackTraceElement[] stackTrace) {
            if (stackTrace != null && stackTrace.length > 0) {
                StringBuilder logs = new StringBuilder(512);
                for (StackTraceElement e : stackTrace) {
                    logs.append(java.text.MessageFormat.format("{0}: {1}(): {2}" , e.getClassName(), e.getMethodName(),
                            e.getLineNumber())).append("\n");
                }
                return logs.toString();
            }
            return "";
        }
    }

}

自定义 ThreadGroup

除了可以自定义线程池,也可以自定义 ThreadGroup,这样就不会调用默认的 ThreadGroup,走 System.err 的逻辑了。

覆盖其uncaughtException方法。

@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {

    @Test
    void liuTest() {

        ThreadPoolExecutor executor = poolExecutor(1, 1);
        log.info("开始提交任务");
        executor.execute(LiuTest::doSomeThing);
        log.info("提交任务完成");

    }

    /*public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
    }*/
    public static ThreadPoolExecutor poolExecutor(int core, int max) {
        return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(), r -> new Thread(new CustomThreadGroup("CustomThreadGroup"), r),
                new ThreadPoolExecutor.AbortPolicy());
    }


    static class CustomThreadGroup extends ThreadGroup {

        public CustomThreadGroup(String name) {
            super(name);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            super.uncaughtException(t, e);
            log.error(e.getMessage());
            log.error("CustomThreadPoolExecutor execute Exception: {}",
                    ThreadUtils.stackTrace(e.getStackTrace()));
        }
    }

    private static ReturnModel doSomeThing() {

        int result ;
       /* try {
            int value = 10 / 0;
            result = 2;
        } catch (Exception e) {
            log.error("doSomeThing execute Exception: ", e);
            return ReturnModel.newFailureInstance();
        }*/
        int value = 10 / 0;
        result = 2;
        return ReturnModel.newSuccessInstance(result);
    }


    static class CustomThreadPoolExecutor extends ThreadPoolExecutor {

        public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                        BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
        }

        @Override
        protected void afterExecute(Runnable r, Throwable e) {
            super.afterExecute(r, e);
            log.error(e.getMessage());
            log.error("CustomThreadPoolExecutor execute Exception: "+
                    ThreadUtils.stackTrace(e.getStackTrace()));
        }
    }


    static class ThreadUtils {


        public static String stackTrace(StackTraceElement[] stackTrace) {
            if (stackTrace != null && stackTrace.length > 0) {
                StringBuilder logs = new StringBuilder(512);
                for (StackTraceElement e : stackTrace) {
                    logs.append(java.text.MessageFormat.format("{0}: {1}(): {2}" , e.getClassName(), e.getMethodName(),
                            e.getLineNumber())).append("\n");
                }
                return logs.toString();
            }
            return "";
        }
    }

}

运行结果同上
尤其注意:上面三种方式针对的都是通过execute(xx)的方式提交任务,如果你提交任务用的是submit()方法,那么上面的三种方式都将不起作用,而应该使用下面的方式

采用Future模式

如果提交任务的时候使用的方法是submit,那么该方法将返回一个Future对象,所有的异常以及处理结果都可以通过future对象获取。 采用Future模式,将返回结果以及异常放到Future中,在Future中处理

@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {

    @Test
    void liuTest()  {

        List<Future<String>> results = new ArrayList<Future<String>>();
        ThreadPoolExecutor es = poolExecutor(1, 1);
        log.info("开始提交任务");
        for(int i=0; i<100;i++)
            results.add(es.submit(new Task()));

        for(Future<String> res : results){
            try{

               int value = 10 / 0;
                System.out.println(res.get());
            }catch (Exception e){
                log.error("CustomThreadPoolExecutor execute Exception: {}",
                        e.getMessage());
            }
        }

        log.info("提交任务完成");

    }

    static class Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            String tid = String.valueOf(Thread.currentThread().getId());
            System.out.printf("Thread#%s : in call\n", tid);
            return tid;
        }
    }

总结

ThreadPoolExecutor
sumbit() 方式底层使用 FutureTask 执行任务,如果业务抛出异常,只有在调用 Future#get() 时才会被抛出。

execute() 方法底层使用 Thread 执行任务,如果业务抛出异常,默认采用 Sysstem.err 进行输出,只会打印在控制台和 tomcat 的 catalina.out 文件,不会输出到日志中。

sumbit() 方法处理异常,既可以在业务中进行手动 catch,也可以在调用 Future#get() 时手动 catch。

execute() 方法处理异常:

  • 业务中手动 catch,每个业务地方都要写,最稳妥。

  • 自定义 ThreadPoolExecutor 或者自定义 ThreadGroup,控制台会打印两遍日志。

  • 设置 UncaughtExceptionHandler,控制台只打印一遍日志。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值