异步线程异常如何捕捉

Java的异常在线程之间不是共享的,在线程中抛出的异常是线程自己的异常,主线程并不能捕获到。也就是说你把线程执行的代码看成另一个主函数。

一、单独线程的异常捕捉

在Thread中,Java提供了一个setUncaughtExceptionHandler的方法来设置线程的异常处理函数,你可以把异常处理函数传进去,当发生线程的未捕获异常的时候,由JVM来回调执行。

//写个异常,int肯定格式化转换失败抛 java.lang.NumberFormatException:异常
public class ThreadExceptionRun implements Runnable {


    @Override
    public void run() {
        int i = Integer.parseInt("uncaughtException");
        System.out.println(i);
    }
}

//写个异常处理器,实现Thread.UncaughtExceptionHandler重写uncaughtException方法
public class ThreadException implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("处理异常信息");
    }
}

3.使用

public class ThreadExceptionMain {
    public static void main(String[] args) {
        Thread thread  = new Thread(new ThreadExceptionRun());
        thread.setUncaughtExceptionHandler(new ThreadException());
        thread.start();
    }
}

二、线程池异步线程异常捕捉

2.1 run方法里面try/catch所有处理逻辑

public class ThreadPoolException {
    public static void main(String[] args) {
        
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        //当线程池抛出异常后 submit无提示,其他线程继续执行
        executorService.submit(new task());

        //当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务
        executorService.execute(new task());
    }
}
// 任务类
class task implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("进入了task方法!!!");
            int i = 1 / 0;
        } catch (Exception e) {
            System.out.println("使用了try -catch 捕获异常" + e);
        }
    }
}

2.2 重写ThreadPoolExecutor.afterExecute方法

public class ThreadPoolException3 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {


        //1.创建一个自己定义的线程池
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10)
        ) {
            //重写afterExecute方法
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("afterExecute里面获取到异常信息,处理异常" + t.getMessage());
            }
        };
        
        //当线程池抛出异常后 execute
        executorService.execute(new task());
    }
}

class task3 implements Runnable {
    @Override
    public void run() {
        System.out.println("进入了task方法!!!");
        int i = 1 / 0;
    }
}

如果要用这个afterExecute处理submit提交的异常, 要额外处理。判断Throwable是否是FutureTask,如果是代表是submit提交的异常,代码如下:

public class ThreadPoolException3 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {


        //1.创建一个自己定义的线程池
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10)
        ) {
            //重写afterExecute方法
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                //这个是excute提交的时候
                if (t != null) {
                    System.out.println("afterExecute里面获取到excute提交的异常信息,处理异常" + t.getMessage());
                }
                //如果r的实际类型是FutureTask 那么是submit提交的,所以可以在里面get到异常
                if (r instanceof FutureTask) {
                    try {
                        Future<?> future = (Future<?>) r;
                        //get获取异常
                        future.get();

                    } catch (Exception e) {
                        System.out.println("afterExecute里面获取到submit提交的异常信息,处理异常" + e);
                    }
                }
            }
        };
        //当线程池抛出异常后 execute
        executorService.execute(new task());
        
        //当线程池抛出异常后 submit
        executorService.submit(new task());
    }
}

class task3 implements Runnable {
    @Override
    public void run() {
        System.out.println("进入了task方法!!!");
        int i = 1 / 0;
    }
}

2.3 使用submit执行任务

我们知道在使用submit执行任务,该方法将返回一个Future对象,不仅仅是任务的执行结果,异常也会被封装到Future对象中,通过get()方法获取。

execute方法被线程工厂factory中设置的 UncaughtExceptionHandler捕捉到异常,而submit方法却没有任何反应!说明UncaughtExceptionHandler在submit中并没有被调用。这是为什么呢?

在日常使用中,我们知道,execute和submit最大的区别就是execute没有返回值,submit有返回值。submit返回的是一个future ,可以通过这个future取到线程执行的结果或者异常信息。

submit源码在底层还是调用的execute方法,只不过多一层Future封装,并返回了这个Future,这也解释了为什么submit会有返回值

//submit()方法
 public <T> Future<T> submit(Callable<T> task) {
     if (task == null) throw new NullPointerException();
     
     //execute内部执行这个对象内部的逻辑,然后将结果或者异常 set到这个ftask里面
     RunnableFuture<T> ftask = newTaskFor(task); 
     // 执行execute方法
     execute(ftask); 
     //返回这个ftask
     return ftask;
 }

可以看到submit也是调用的execute,在execute方法中,我们的任务被提交到了addWorker(command, true) ,然后为每一个任务创建一个Worker去处理这个线程,这个Worker也是一个线程,执行任务时调用的就是Worker的run方法!run方法内部又调用了runworker方法!如下所示:

public void run() {
        runWorker(this);
 }
     
final void runWorker(Worker w) {
     Thread wt = Thread.currentThread();
     Runnable task = w.firstTask;
     w.firstTask = null;
     w.unlock(); // allow interrupts
     boolean completedAbruptly = true;
     try {
      //这里就是线程可以重用的原因,循环+条件判断,不断从队列中取任务        
      //还有一个问题就是非核心线程的超时删除是怎么解决的
      //主要就是getTask方法()见下文③
         while (task != null || (task = getTask()) != null) {
             w.lock();
             if ((runStateAtLeast(ctl.get(), STOP) ||
                  (Thread.interrupted() &&
                   runStateAtLeast(ctl.get(), STOP))) &&
                 !wt.isInterrupted())
                 wt.interrupt();
             try {
                 beforeExecute(wt, task);
                 Throwable thrown = null;
                 try {
                  //执行线程
                     task.run();
                     //异常处理
                 } catch (RuntimeException x) {
                     thrown = x; throw x;
                 } catch (Error x) {
                     thrown = x; throw x;
                 } catch (Throwable x) {
                     thrown = x; throw new Error(x);
                 } finally {
                  //execute的方式可以重写此方法处理异常
                     afterExecute(task, thrown);
                 }
             } finally {
                 task = null;
                 w.completedTasks++;
                 w.unlock();
             }
         }
         //出现异常时completedAbruptly不会被修改为false
         completedAbruptly = false;
     } finally {
      //如果如果completedAbruptly值为true,则出现异常,则添加新的Worker处理后边的线程
         processWorkerExit(w, completedAbruptly);
     }
 }

核心就在 task.run(); 这个方法里面了, 期间如果发生异常会被抛出。

  1. 如果用execute提交的任务,会被封装成了一个runable任务,然后进去 再被封装成一个worker,最后在worker的run方法里面调用runWoker方法, runWoker方法里面执行任务任务,如果任务出现异常,用try-catch捕获异常往外面抛,我们在最外层使用try-catch捕获到了 runWoker方法中抛出的异常。因此我们在execute中看到了我们的任务的异常信息。
  2. 那么为什么submit没有异常信息呢? 因为submit是将任务封装成了一个futureTask ,然后这个futureTask被封装成worker,在woker的run方法里面,最终调用的是futureTask的run方法, 猜测里面是直接吞掉了异常,并没有抛出异常,因此在worker的runWorker方法里面无法捕获到异常。

一条小咸鱼