所有分类
  • 所有分类
  • 未分类

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

简介

说明

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

本文内容概述

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

  1. Thread + execute
  2. Runnable + execute
  3. 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

注意:下边必须要用execute执行,不要用submit。因为submit发生异常时获取不到Throwable(Throwable是null)。

法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.getMessage());
        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.execute(myThread);
        executor.shutdown();
        System.out.println("主线程结束");
    }
}

执行结果

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

场景2:Runnable + execute

注意:下边必须要用execute执行,不要用submit。因为submit发生异常时获取不到Throwable(Throwable是null)。

法1:自定义ThreadPoolExecutor

自定义ThreadPoolExecutor, 实现Thread.UncaughtExceptionHandler接口的uncaughtException方法。

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 thread, Throwable t) {
        System.out.println("捕获到异常。异常信息为:" + t.getMessage());
        System.out.println("异常栈信息为:");
        t.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:运行开始
主线程结束
捕获到异常。异常信息为:/ by zero
异常栈信息为:
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)

法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 thread, Throwable t) {
        System.out.println("捕获到异常。异常信息为:" + t.getMessage());
        System.out.println("异常栈信息为:");
        t.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:运行开始
主线程结束
捕获到异常。异常信息为:/ by zero
异常栈信息为:
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)

法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 thread, Throwable t) {
        System.out.println("捕获到异常。异常信息为:" + t.getMessage());
        System.out.println("异常栈信息为:");
        t.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:运行开始
主线程结束
捕获到异常。异常信息为:/ by zero
异常栈信息为:
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)

场景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.getMessage());
                System.out.println("异常栈信息为:");
                t.printStackTrace();
            }
        } else if (r instanceof FutureTask) {
            FutureTask futureTask = (FutureTask) r;
            try {
                futureTask.get();
            } catch (InterruptedException e) {
                System.out.println("捕获到InterruptedException异常。异常信息为:" + e.getMessage());
                System.out.println("异常栈信息为:");
                e.printStackTrace();
            } catch (ExecutionException e) {
                System.out.println("捕获到ExecutionException异常。异常信息为:" + e.getMessage());
                System.out.println("异常栈信息为:");
                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.lang.ArithmeticException: / by zero
异常栈信息为:
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:38)
	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

原理

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 ,这样如果疏漏了异常捕获,也不至于丢掉异常信息。 ​

1

评论4

请先

  1. 方案1中的 executor.submit(myThread)捕获不到异常,应该是execute
    珠光2023 2023-11-29 0
    • 我自测是能捕获到的呀,文章中的打印结果都是真实的打印,已经输出了捕获到异常时的信息。
      自学精灵 2023-11-29 0
      • System.out.println("捕获到异常。异常栈信息为:" + t.getMessage()); 通过t.getMessage()是获取不到内容的
        珠光2023 2023-11-29 0
显示验证码
没有账号?注册  忘记密码?

社交账号快速登录