Java中子线程的异常处理

最近在面试的过程中被问到子线程中异常有几种处理方式,突然意识到没有了解过这方面的知识,于是通过这篇学习笔记来记录下Java中子线程的异常处理方式。

子线程异常处理方式

通过学习,了解到子线程中的异常处理方式有如下三种形式,我们依次通过案例来使用下这三种方式。

  • 在子线程中捕获异常并处理
  • 为线程设置"未捕获异常处理器"UncaughtExceptionHandler
  • 通过Future的get方法捕获异常

在子线程中捕获异常并处理

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadException {
    //实现Runnable接口
    static class MyTask implements Runnable {
        @Override
        public void run() {
            System.out.println("执行线程");
            //捕捉异常
            try {
                int a = 3/0;
            }catch(Exception e) {
                System.out.println("处理异常")
            }
            System.out.println("线程结束");
        }
    }
    public static void main(String[] args) {
        //启动线程
        new Thread(new MyTask()).start();
    }
}

为线程设置"未捕获异常处理器"UncaughtExceptionHandler

设置方式

  • 可以调用Thread.setUncaughtExceptionHandler()设置
  • 如果使用的线程池,可以通过ThreadFactory.setUncaughtExceptionHandler()设置。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadException {
    //实现Runnable接口
    static class MyTask implements Runnable {
        @Override
        public void run() {
            int a = 3/0;
        }
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread thread = new Thread(new MyTask());
        //设置UncaughtExceptionHandler,可以在这个方法中打印日志
        thread.setUncaughtExceptionHandler((Thread,Throwable) -> {
            System.out.println("捕捉到异常了");
        });
        //启动线程
        thread.start();
    }
}

通过Future的get方法捕获异常

注意:对于有返回值的线程来说,如果不调用Future的get方法,假如子线程没有处理异常,那么这个异常会被"吞掉",很难定位问题

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadException {
    static class MyTask implements Callable {
        @Override
        public Object call() throws Exception {
            System.out.println("执行线程");
            //制造异常
            int a = 3/0;
            return 1;
        }
    }
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask<>(new MyTask());
        new Thread(futureTask).start();
        //通过get方法捕获异常
        try {
            futureTask.get();
        }catch (Exception e) {
            System.out.println("捕获到异常了");
        }
    }
}

子线程异常可能被"吞掉"

对于有返回值的线程,也就是实现了Callable接口的线程,子线程发生的异常可能被"吞掉",样例如下

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadException {
    static class MyTask implements Callable {
        @Override
        public Object call() throws Exception {
            System.out.println("执行线程");
            //制造异常
            int a = 3/0;
            return 1;
        }
    }
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask<>(new MyTask());
        //启动线程
        new Thread(futureTask).start();
    }
}

运行结果:

通过上面的运行结果我们发现,控制台什么也没有打印,但是我们子线程中有这么一行代码是会抛出异常的int a =3/0

为什么异常会被"吞掉呢"

我们写的Callable实现类要封装在FutureTask中。

FutureTask futureTask = new FutureTask<>(new MyTask());

我们可以看下FutureTask源码,我们设置的任务首先赋值给了FutureTask内部的callable,然后在去调用FutureTask中的run方法

我们继续看FutureTask的run源码,执行任务并捕获了任务中可能会出现的异常,这里并没有打印,而是通过setException(ex)方法,将抛出的异常会被放到 outcome 对象中,这个对象就是submit()方法会返回的FutureTask对象执行get()方法得到的结果。

所以对于有返回值的任务要么通过FutureTask的get方法去捕捉异常,要么在子线程中就处理掉,不然会出现任务莫名其妙的停止,并且定位不到问题的情况。

posted @ 2020-07-25 18:32  un1que~  阅读(700)  评论(0编辑  收藏  举报