java如何处理多线程异常

一、一个线程在执行过程中发生了异常会怎样?

        那要看我们是否对这个异常进行了处理,如果处理了,那么线程会继续执行,如果没有处理,那么线程会释放掉自己所持有的锁,退出执行,如果这个线程是主线程,那么主线程退出执行了,程序也会停止执行,如果这个线程不是主线程,那么它的退出不会影响到主线程和其他线程,程序会继续执行。但无论这个线程是否是主线程,线程因异常而退出会导致我们的业务执行失败,会影响正常的业务功能,所以我们应该在开发中用合适的方式去处理这些异常。

        java中的异常分为检查时异常和运行时异常两大类,java要求我们必须显示地处理检查时异常,如果不处理,会在编译期报错,而对于运行时异常我们可以处理,也可以不处理,都是可以通过编译的,只是,如果不处理,那么可能在程序的运行期间因为发生RuntimeExeception而导致线程异常退出。那么对于运行时异常我们应该如何处理呢?

        首先,在单线程环境中,我们可以通过try/catch语句块去进行异常的捕获处理,或者是用throw/throws方式将异常向上抛出,而对于抛出的异常,可以由外层的调用方法来进行捕获处理。那么多线程执行过程中发生的异常如何处理呢?

二、处理多线程执行过程中发生的异常

1、Runnable类型任务的异常处理

        因为Runnable的run方法是无法将异常向上抛出的,所以它在执行过程中如果发生了异常,这个异常要么在run方法内部处理,要么由任务线程的UncaughtExceptionHandler来处理,如果我们既没有在run方法内部捕获它,又没为任务线程设置UncaughtExceptionHandler,那么这个异常的发生将导致任务线程异常退出,任务执行失败。

未处理异常的情况:

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("模拟Runnable异常...");
            int num = 1/0;
        }
    });
    thread.start();
}

 

在run方法内部处理异常:

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("模拟Runnable异常...");
            try {
                int num = 1/0;
            }catch (Exception e){
                System.out.println("Runnable任务内部处理异常");
            }
        }
    });
    thread.start();
}

用UncaughtExceptionHandler处理未捕获异常:

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("模拟Runnable异常...");
            int num = 1/0;
        }
    });
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
    thread.start();
}

UncaughtExceptionHandler

        UncaughtExceptionHandler是定义在Thread类中的一个内部接口,它是线程的未捕获异常处理器,它的作用是处理线程执行过程中发生的未捕获异常,当线程在执行过程中发生了异常时,jvm会先去查找当前方法有没有对这个异常做捕获处理,也就是查找当前方法的catch语句块,如果没做捕获处理,则查找当前方法是否声明抛出了这个异常,如果声明抛出这一类异常了,则会沿着方法调用栈往上查找,如果调用栈中各个方法均声明抛出了这一类异常,而一直查找到了栈底却依然没有对于这个异常的捕获处理,则会去查找此线程有没有设置UncaughtExceptionHandler,如果设置了,则由这个UncaughtExceptionHandler处理,如果没设置,则再去查找当前线程所属线程组有没有设置UncaughtExceptionHandler,如果设置了,就由它来处理,如果没设置,则该异常被定位到System.err。Thread类中还定义了一个setUncaughtExceptionHandler方法,用于为线程设置未捕获异常处理器,但是UncaughtExceptionHandler只对Runnable类型的任务线程起作用,当线程执行的是Callable类型的任务时,即便我们为任务线程设置了UncaughtExceptionHandler,它也不会生效。

2、Callable类型任务的异常处理

        UncaughtExceptionHandler在Callable类型的任务线程里是不生效的

public static void main(String[] args) {
    FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            System.out.println("模拟Callable异常...");
            int num = 1/0;
            return num;
        }
    });
    Thread thread = new Thread(futureTask);
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
    thread.start();
}

        

        可见,当call方法执行过程中发生了未捕获异常时,这个异常也不会被UncaughtExceptionHandler捕获到,为什么呢?我们在main线程中调用一下FutureTask对象的get方法看一下会有怎样的结果:

public static void main(String[] args) {
    FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            System.out.println("模拟Callable异常...");
            int num = 1/0;
            return num;
        }
    });
    Thread thread = new Thread(futureTask);
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
    thread.start();
    try {
        futureTask.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

        可以看到,任务线程中的异常被抛到了get方法的调用线程main线程这里,可见,即便是Callable任务执行过程中发生了异常,这个异常也不会在任务线程里出现,当然也就不能被任务线程的UncaughtExceptionHandler捕获到,它只会在调用FutureTask对象的get方法的线程里出现,并且它被包装成了一个ExecutionException。那么在这个过程中到底发生了什么呢?

FutureTask对于Callable任务异常的处理

        Callable类型的任务线程在被调度到的时候,执行的是FutureTask对象的run方法,而在run方法的内部执行的又是Callable对象的call方法,如果call方法在执行过程中发生了异常,那么这个异常会被run方法捕获处理,FutureTask类有一个Object类型的成员变量outcome,这个成员变量用于存放call方法的执行结果或call方法在执行过程中抛出的异常,而outcome存放的异常在当前的FutureTask对象的get方法中又被重新包装成了一个ExecutionException异常抛到了get方法外部,也就是调用get方法的线程中。源码如下:

再来看一下setException方法的源码:

这就是在任务线程内部捕获不到call方法异常的原因。再看一下get方法:

 

        所以对于Callable任务的异常,我们可以在任务内部【也就是call方法内部】捕获原发类型的异常来处理,或者在FutureTask的get方法的调用线程里捕获ExecutionException类型的异常来处理。

3、线程池中任务异常的处理

        当我们使用的是线程池的execute方法来提交多线程任务时,因为它提交的是Runnable类型的任务,所以我们为任务线程设置的UncaughtExceptionHandler是可以生效的,所以我们可以实现一个ThreadFactory,再实现一个UncaughtExceptionHandler,在ThreadFactory的newThread方法内部,为每一个创建出来的线程都设置这个UncaughtExceptionHandler,让线程使用这个UncaughtExceptionHandler去处理任务执行过程中发生的未捕获异常。

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new LinkedBlockingDeque<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "模拟Runnable异常");
                int num = 1/0;
            }
        });
    }
}

        所以当我们用execute方法往线程池中提交任务时,既可以在任务内部【run方法内部】处理异常,也可以为线程设置UncaughtExceptionHandler来处理任务的未捕获异常。

        但是用submit方法提交到线程池中的任务所抛出的异常却无法被任务线程的UncaughtExceptionHandler所捕获到

比如,当提交的是Callable类型的任务时:

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new ArrayBlockingQueue<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
        threadPoolExecutor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("模拟Callable异常");
                int num = 1/0;
                return num;
            }
        });

    }
}

可见UncaughtExceptionHandler没有生效,再调用一下submit方法返回的Future对象的get方法看一下结果: 

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new ArrayBlockingQueue<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
        Future<Integer> result = threadPoolExecutor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("模拟Callable异常");
                int num = 1/0;
                return num;
            }
        });
        try {
            result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

         Callable任务的异常被包装为ExecutionException异常抛到了调用get方法的main线程中,原因也很简单,submit方法肯定将我们提交的Callable任务封装到了FutureTask对象中执行,而FutureTask对象的run方法对call方法抛出的异常做了捕获处理,将异常放在了自己的outcome变量中保存。

submit方法的源码也确实是这样实现的:

 

FutureTask.run方法源码就不贴了,上面已经贴过了。 

         而即便是我们用submit方法提交的是一个Runnable类型的任务,任务线程的UncaughtExceptionHandler依然不会生效:

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new ArrayBlockingQueue<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
       Future result = threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("模拟Runnable异常");
                int num = 1/0;
            }
        });
        try {
            result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

         任务异常被包装为ExecutionException抛到了调用Future.get方法的main线程中,而未被任务线程的UncaughtExceptionHandler捕获到。原因是,submit方法用我们传入的Runnable对象构造了一个FutureTask对象去执行,并将Runnable对象转换为Callable类型赋给了这个FutureTask对象的callable成员变量。

源码:

因此这个异常只能在调用Future.get方法的线程中以ExecutionException类型捕获到。

        总结一下,使用execute方法提交的多线程任务,异常可以在任务内部处理,也可以为任务线程设置UncaughtExceptionHandler处理。使用submit方法提交的多线程任务,异常可以在任务内部处理,也可以在调用线程中处理,也就是在调用Future对象的get方法去获取任务结果的线程中处理。

        在实际的开发工作中,我们要根据业务需求去选择使用哪种方式提交多线程任务,当我们选定方式之后,还要去使用合适的策略去处理任务异常。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值