Java并发07:线程异常处理知多少?

169 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

学习MOOC视频记录的笔记

  • Java异常体系图

  • 实际工作中,如何全局处理异常?为什么要全局处理?不处理行不行?

1.线程的未捕获异常 UncaughtException 应该如何处理?

1.1 为什么需要 UncaughtExceptionHandler

  • 主线程可以轻松发现异常,子线程却不行
public class ExceptionInChildThread implements Runnable {
    public static void main(String[] args) {
        new Thread(new ExceptionInChildThread()).start();
 
        for (int i = 0; i < 1000; i++) {
            System.out.println("i = " + i);
        }
    }
 
    @Override
    public void run() {
        throw new RuntimeException();
    }
}

子线程抛出异常,主线程的运行丝毫不受影响。

i = 0
i = 1
Exception in thread "Thread-0" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.ExceptionInChildThread.run(ExceptionInChildThread.java:18)
    at java.lang.Thread.run(Thread.java:748)
i = 2
i = 3
  • 子线程异常无法用传统方法捕获
/**
* 1. 不加 try catch 抛出4个异常,都带线程名字
* 2. 加了 try catch 期望捕获到第一个线程的异常,线程234不应该运行,希望看到打印出Caught Exception
* 3. 执行时发现,根本没有 Caught Exception,线程234依然运行并且抛出异常
*
* 说明线程的异常不能用传统方法捕获
*/
public class CantCatchDirectly implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        new Thread(new CantCatchDirectly(), "MyThread-1").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-2").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-3").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-4").start();
    }
 
    @Override
    public void run() {
        throw new RuntimeException();
    }
}

直接抛出异常:

Exception in thread "MyThread-1" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:23)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "MyThread-2" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:23)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "MyThread-3" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:23)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "MyThread-4" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:23)

尝试使用 try-catch 来捕获异常:

public class CantCatchDirectly implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        try {
            new Thread(new CantCatchDirectly(), "MyThread-1").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(), "MyThread-2").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(), "MyThread-3").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(), "MyThread-4").start();
        } catch (InterruptedException e) {
            System.out.println("Caught Exception");
        }
    }
 
    @Override
    public void run() {
        throw new RuntimeException();
    }
}

运行结果却没有改变:

Exception in thread "MyThread-1" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:27)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "MyThread-2" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:27)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "MyThread-3" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:27)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "MyThread-4" java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:27)

出现问题的原因是:抛出异常是在子线程中抛出的,但是使用try-catch捕获异常却是在主线程中进行的,因此捕获失效了。

  • 不能直接捕获的后果、提高健壮性

1.2 两种解决方案

方案一(不推荐):手动在每个 run 方法里进行 try catch

public class CantCatchDirectly implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        new Thread(new CantCatchDirectly(), "MyThread-1").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-2").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-3").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-4").start();
    }
 
    @Override
    public void run() {
        try {
            throw new RuntimeException();
        } catch (RuntimeException e) {
            System.out.println("Caught Exception");
        }
    }
}

由输出可见异常被捕获了:

Caught Exception
Caught Exception
Caught Exception
Caught Exception

方案二(推荐):利用 UncaughtExceptionHandler

该接口中只有一个方法:void uncaughtException(Thread t,Throwable e);

异常处理器的调用策略

默认异常处理器首先检查有没有父线程,一直找到最顶层的异常处理器

image-20221110012248060

自己实现

  • 给程序统一设置
  • 给每个线程单独设置
  • 给线程池设置

异常捕获器

/**
* 自己的MyUncaughtExceptionHandler
*/
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
 
    private String name;
 
    public MyUncaughtExceptionHandler(String name) {
        this.name = name;
    }
 
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.WARNING, "线程异常,终止啦" + t.getName(), e);
        System.out.println(name + "捕获了异常" + t.getName() + "异常" + e);
    }
}

捕获子线程异常

public class UseOwnUncaughtExceptionHandler implements Runnable {
 
    public static void main(String[] args) throws InterruptedException {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1"));
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-1").start();
        Thread.sleep(300);
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-2").start();
        Thread.sleep(300);
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-3").start();
        Thread.sleep(300);
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-4").start();
    }
 
    @Override
    public void run() {
        throw new RuntimeException();
    }
}

日志:

十一月 10, 2022 1:31:07 上午 threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常,终止啦MyThread-1
java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.UseOwnUncaughtExceptionHandler.run(UseOwnUncaughtExceptionHandler.java:18)
    at java.lang.Thread.run(Thread.java:748)
 
捕获器1捕获了异常MyThread-1异常java.lang.RuntimeException
十一月 10, 2022 1:31:08 上午 threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常,终止啦MyThread-2
java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.UseOwnUncaughtExceptionHandler.run(UseOwnUncaughtExceptionHandler.java:18)
    at java.lang.Thread.run(Thread.java:748)
 
捕获器1捕获了异常MyThread-2异常java.lang.RuntimeException
捕获器1捕获了异常MyThread-3异常java.lang.RuntimeException
十一月 10, 2022 1:31:08 上午 threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常,终止啦MyThread-3
java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.UseOwnUncaughtExceptionHandler.run(UseOwnUncaughtExceptionHandler.java:18)
    at java.lang.Thread.run(Thread.java:748)
 
十一月 10, 2022 1:31:08 上午 threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常,终止啦MyThread-4
java.lang.RuntimeException
    at threadcoreknowledge.uncaughtexception.UseOwnUncaughtExceptionHandler.run(UseOwnUncaughtExceptionHandler.java:18)
    at java.lang.Thread.run(Thread.java:748)
 
捕获器1捕获了异常MyThread-4异常java.lang.RuntimeException

2.线程的未捕获异常–

常见面试问题

  • Java异常体系

  • 如何全局处理异常?为什么要全局处理?不处理行不行?

使用全局的 UncaughtExceptionHandler,前端处理以及后端日志保存

  • run方法是否可以抛出异常?如果抛出异常,线程的状态会怎么样?

方法层面不能向外抛,只能自己处理。如果抛出异常,线程会终止运行,打印异常堆栈

  • 线程中如何处理某个未处理异常

使用全局的 UncaughtExceptionHandler