Java线程池系列--全局异常处理的方法(有实例)

简介

说明

本文用示例介绍如何全局捕获Java线程池中的异常。

本文内容概述

本文先用示例介绍不捕获异常时的现象(即:“实例:不捕获异常”),再针对线程池的任务提交的三种方式分别说明异常处理的方法。线程池的任务提交的三种方式如下:

  1. Thread

  1. Runnable + execute

  1. Callable + submit

实例:不捕获异常

说明

当抛出RuntimeException异常时,线程就会终结,而对于主线程和其他线程完全不受影响,且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常)。

实例

package com.example.a;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
class MyThread extends Thread {
    private String name;
 
    public MyThread(String name) {
        this.name = name;
    }
 
    @Override
    public void run() {
        System.out.println(name + ":运行开始");
        int i = 1 / 0;
        System.out.println(name + ":运行结束");
    }
}
 
public class Demo {
    public static void main(String[] args) {
        System.out.println("主线程开始");
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(new MyThread("线程1"));
        executor.shutdown();
        System.out.println("主线程结束");
    }
}

执行结果

主线程开始
主线程结束
线程1:运行开始
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    at com.example.a.MyThread.run(Demo.java:16)
    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:748)

方案1:Thread

法1:自定义ThreadPoolExecutor

自定义ThreadPoolExecutor, 重写afterExecute方法

package com.example.a;
 
import java.util.concurrent.*;
 
class MyThread extends Thread {
    private String name;
 
    public MyThread(String name) {
        this.name = name;
    }
 
    @Override
    public void run() {
        System.out.println(name + ":运行开始");
        int i = 1 / 0;
        System.out.println(name + ":运行结束");
    }
}
 
class MyThreadPoolExecutor extends ThreadPoolExecutor {
 
    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
 
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        System.out.println("捕获到异常。异常栈信息为:");
        t.printStackTrace();
    }
}
 
public class Demo {
    public static void main(String[] args) {
        System.out.println("主线程开始");
        ExecutorService executor = new MyThreadPoolExecutor(5,50, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
        MyThread myThread = new MyThread("线程1");
        executor.submit(myThread);
        executor.shutdown();
 
        System.out.println("主线程结束");
    }
}

执行结果

主线程开始
线程1:运行开始
主线程结束
捕获到异常。异常栈信息为:
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
    at com.example.a.MyThreadPoolExecutor.afterExecute(Demo.java:30)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1157)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

方案2:Runnable + execute

法1:自定义ThreadPoolExecutor

自定义ThreadPoolExecutor, 重写afterExecute方法

package com.example.a;
 
import java.util.concurrent.*;
 
class MyRunnable implements Runnable {
    private String name;
 
    public MyRunnable(String name) {
        this.name = name;
    }
 
    @Override
    public void run() {
        System.out.println(name + ":运行开始");
        int i = 1 / 0;
        System.out.println(name + ":运行结束");
    }
}
 
class MyThreadPoolExecutor extends ThreadPoolExecutor {
 
    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
 
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        System.out.println("捕获到异常。异常栈信息为:");
        t.printStackTrace();
    }
}
 
public class Demo {
    public static void main(String[] args) {
        System.out.println("主线程开始");
        ExecutorService executor = new MyThreadPoolExecutor(5,50, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
        MyRunnable myRunnable = new MyRunnable("线程1");
        executor.execute(myRunnable);
        executor.shutdown();
 
        System.out.println("主线程结束");
    }
}

执行结果

主线程开始
线程1:运行开始
主线程结束
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
    at com.example.a.MyRunnable.run(Demo.java:15)
    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:748)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    at com.example.a.MyRunnable.run(Demo.java:15)
    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:748)

若改为executor.submit(myRunnable); 结果如下(报空指针错误):

主线程开始
主线程结束
线程1:运行开始
捕获到异常。异常栈信息为:
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
    at com.example.a.MyThreadPoolExecutor.afterExecute(Demo.java:30)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1157)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

法2:ThreadFactory

自定义ThreadFactory, 给线程工厂生产出来的Thread实例设置UncaughtExceptionHandler

package com.example.a;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
 
class MyThread extends Thread {
    private String name;
 
    public MyThread(String name) {
        this.name = name;
    }
 
    @Override
    public void run() {
        System.out.println(name + ":运行开始");
        int i = 1 / 0;
        System.out.println(name + ":运行结束");
    }
}
 
class MyUncheckedExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("捕获到异常。异常栈信息为:");
        e.printStackTrace();
    }
}
 
public class Demo {
    public static void main(String[] args) {
        System.out.println("主线程开始");
        ExecutorService exec = Executors.newSingleThreadExecutor(new ThreadFactory(){
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
                return thread;
            }
        });
        exec.execute(new MyThread("线程1"));
        exec.shutdown();
        System.out.println("主线程结束");
    }
}

执行结果

主线程开始
线程1:运行开始
主线程结束
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
    at com.example.a.MyThread.run(Demo.java:17)
    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:748)

若改为executor.submit(myRunnable); 结果如下:(捕获不到异常)

主线程开始
主线程结束
线程1:运行开始

法3:默认的handler

package com.example.a;
 
import java.util.concurrent.*;
 
class MyRunnable implements Runnable {
    private String name;
 
    public MyRunnable(String name) {
        this.name = name;
    }
 
    @Override
    public void run() {
        System.out.println(name + ":运行开始");
        int i = 1 / 0;
        System.out.println(name + ":运行结束");
    }
}
 
class MyUncheckedExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("捕获到异常。异常栈信息为:");
        e.printStackTrace();
    }
}
 
public class Demo {
    public static void main(String[] args) {
        System.out.println("主线程开始");
        Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
        ExecutorService executor = Executors.newSingleThreadExecutor();
        MyRunnable myRunnable = new MyRunnable("线程1");
        executor.execute(myRunnable);
        executor.shutdown();
 
        System.out.println("主线程结束");
    }
}
主线程开始
主线程结束
线程1:运行开始
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
    at com.example.a.MyRunnable.run(Demo.java:15)
    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:748)

若改为executor.submit(myRunnable); 结果如下:(捕获不到异常)

主线程开始
线程1:运行开始
主线程结束

方案3:Callable + submit

法1:自定义ThreadPoolExecutor

说明

自定义ThreadPoolExecutor, 重写afterExecute方法

实例

package com.example.a;
 
import java.util.concurrent.*;
 
class MyCallable implements Callable {
    private String name;
 
    public MyCallable(String name) {
        this.name = name;
    }
 
    @Override
    public Object call() throws Exception {
        System.out.println(name + ":运行开始");
        int i = 1 / 0;
        return null;
    }
}
 
class MyThreadPoolExecutor extends ThreadPoolExecutor {
 
    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
 
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (r instanceof Thread) {
            if (t != null) {
                System.out.println("捕获到Thread异常。异常栈信息为:");
                t.printStackTrace();
            }
        } else if (r instanceof FutureTask) {
            FutureTask futureTask = (FutureTask) r;
            try {
                futureTask.get();
            } catch (InterruptedException e) {
                System.out.println("InterruptedException。异常栈信息为:");
                e.printStackTrace();
            } catch (ExecutionException e) {
                System.out.println("捕获到ExecutionException异常。异常栈信息为:");
                e.printStackTrace();
            }
        }
    }
}
 
public class Demo {
    public static void main(String[] args) {
        System.out.println("主线程开始");
        ExecutorService executor = new MyThreadPoolExecutor(5,50, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
        MyCallable myCallable = new MyCallable("线程1");
        executor.submit(myCallable);
        executor.shutdown();
 
        System.out.println("主线程结束");
    }
}
主线程开始
主线程结束
线程1:运行开始
捕获到ExecutionException异常。异常栈信息为:
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at com.example.a.MyThreadPoolExecutor.afterExecute(Demo.java:37)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1157)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ArithmeticException: / by zero
    at com.example.a.MyCallable.call(Demo.java:15)
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    ... 2 more

原理

其他网址

线程池异常处理详解,一文搞懂_helloworld-CSDN博客_线程池异常

execute和submit有什么区别?

execute

execute提交的任务,会被封装成一个Runable任务,然后Runable对象被封装成一个Worker,最后在Worker的run方法里面跑runWoker方法, 里面再又调了我们最初的参数 Runable任务的任务,并且用try-catch捕获了异常,会被直接抛出去,因此我们在execute中看到了我们的任务的异常信息。

submit

submit提交的任务,会被封装成一个Runable任务,然后Runable对象被封装成一个FutureTask ,里面的 run 方法 try-catch了所有的异常,并设置到了outcome(Object类型)里面, 可以通过FutureTask .get获取到outcome。

所以在submit提交的时候,里面发生了异常, 是不会有任何抛出信息的。在submit里面,除了从返回结果里面取到异常之外, 没有其他方法了。

因此,在不需要返回结果的情况下,最好用execute ,这样如果疏漏了异常捕获,也不至于丢掉异常信息。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值