线程异常捕捉_异常处理与线程相关问题

919c18b24f0a347262204a29704d8d17.png

Exception 和 RuntimeException 区别

  • 在定义方法时不需要声明会抛出runtime exception; 在调用这个方法时不需要捕获这个runtime exception; runtime exception是从java.lang.RuntimeException或java.lang.Error类衍生出来的。 例如:nullpointexception,IndexOutOfBoundsException就属于runtime exception。
  • 定义方法时必须声明所有可能会抛出的exception; 在调用这个方法时,必须使用try语句进行检查或者在方法上加上throws Exception(捕获指的是catch这个语句块,你可以catch之后就地处理,或者用throw关键字继续抛出,或者你不加catch也行,只要你加上throws Exception就会自动抛出,不过我们一般习惯加上catch,在catch中执行日志记录等操作);exception是从java.lang.Exception类衍生出来的。例如:IOException,SQLException就属于Exception。

a999edb1d17bd9a636f055f6b45e6f6e.png

0035f1c85bdcfcab54f4d2f972677c31.png

8a2631c9a45e47b275b371a8560dc020.png
  • 如果自定义异常类完成全局异常功能,一般选择继承runtime exception,在会报异常的地方抛出,然后由异常捕捉器捕捉处理:
/* 检验密码是否正确 */
if (!sysUser.getPassword().equals(Md5Util.createMd5(loginReqVO.getPassword()))) {
    throw new BusinessException(ExceptionInfoEnum.ERROR_PASSWORD);
}

/* 全局异常处理器 */
@ExceptionHandler(value = BusinessException.class)
public AjaxResult businessException(BusinessException e) {
    log.error(LogConstant.BUSINESSEXCEPTION_PREFIX, e);
    return AjaxResult.builder().withSimpleInject(e).build();
}
  • throws用于方法上,throw用于方法内。
  • 当抛出Exception时必须在方法上throws,抛出RuntimeException时不需要。
  • 在使用框架开发时,如果是异常处理器范围外的异常(指不在controller、service、dao直接能调用范围内的程序出现的异常),自己throw后try catch处理就好了。

如果main方法抛出异常,谁来处理?

如下图所示:

cf05888c04d99c8e0e81b6e0a7cbf769.png

实际上是 Thread 默认的异常处理器为我们提供了这个功能,但是是由 JVM 去调用这个功能

Thread.UncaughtExceptionHandler:

    /**
     * 当线程由于未捕获异常而终止时Java虚拟机将使用getUncaughtExceptionHandler
     * 查询线程的UncaughtExceptionHandler并将调用处理程序的uncaughtException方法,
     * 将线程和异常作为参数传递。如果未明确设置线程的UncaughtExceptionHandler,
     * 则其ThreadGroup对象将作为其UncaughtExceptionHandler。如果ThreadGroup对象
     * 对处理异常没有特殊要求,它可以将调用转发给getDefaultUncaughtExceptionHandler
     * 默认未捕获的异常处理程序。
     */
    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

1d64d3531accd32df7f5d0ff154471c9.png

ThreadGroup是默认的UncaughtExceptionHandler,如果我们没有自定义UncaughtExceptionHandler那么就将使用ThreadGroup,也可以自定义:

3e79d7f44ac175b727a8ed5261ab545d.png

异常对程序造成的影响

3f3bea74925e81ff278da641a7c85a25.png

总结:

  1. try中有异常的地方后面的代码都不会执行;catch中将异常继续throw,则catch块后的代码都不会执行
  2. 如果异常交给JVM处理,异常出现的地方后的代码将不会执行,并且程序直接结束
  3. 你可以理解try catch只是起到记录日志的作用,我不管加不加try catch,异常都会沿着调用栈往上抛,区别你可以选择在catch中记录完日志后继续throw或者直接停住

线程池的异常处理机制

我们知道,在JAVA中,异常在不同的线程之间是不能共享的,比如,你在父线程中try catch一段代码,这段代码中new了一个子线程,子线程中抛出一个异常,那么其实在父线程中是感知不到这个异常的出现的:

f70e4b4c04770c5fccfaa7adde2bc4f6.png

上图中的try catch相当于不存在,主线程和子线程各执行各的,互不相干,

接下来我们看看在线程池中式如何处理线程异常的

一提到线程池,我们第一时间想到的就是juc包下的ThreadPoolExecutor:

ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>(), threadFactory);

ThreadPoolExecutor为我们提供了两种执行自定义任务的方法:

threadPoolExecutor.submit(task);
threadPoolExecutor.execute(task);

submit只能使用try catch在获取子线程异常的时候会造成父线程阻塞:

public class FutureDemo1 {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), threadFactory);
        FutureRunnable runnable = new FutureRunnable();
        Future<?> submit = threadPoolExecutor.submit(runnable);
        try {
            submit.get();
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("子线程的异常被父线程catch了");
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
        System.out.println("程序结束");
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                threadPoolExecutor.shutdown();
                break;
            }
        }
    }
}

class FutureRunnable implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("程序正在运行");
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new RuntimeException("报错了!");
    }
}

Future是Java 5添加的类,用来描述一个异步计算的结果,但是获取一个结果时方法较少,要么通过轮询isDone,确认完成后,调用get()获取值,要么调用get()设置一个超时时间。但是这个get()方法会阻塞住调用线程,这种阻塞的方式显然和我们的异步编程的初衷相违背。
为了解决这个问题,JDK吸收了guava的设计思想,加入了Future的诸多扩展功能形成了CompletableFuture:

public class FutureDemo2 {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), threadFactory);
        FutureRunnable2 runnable2 = new FutureRunnable2();
        CompletableFuture.runAsync(runnable2, threadPoolExecutor)
                .whenComplete((aVoid, throwable) -> System.out.println("程序A结束"))
                .exceptionally(throwable -> {
                    System.out.println("子线程的异常被父线程catch了");
                    runnable2.flg = false;
                    return null;
                });
        System.out.println("我在A执行后马上执行,不会因为子线程而被阻塞");
        while (true) {
            if (!runnable2.flg) {
                System.out.println("程序结束");
                threadPoolExecutor.shutdown();
                break;
            }
        }
    }
}

class FutureRunnable2 implements Runnable {
    volatile boolean flg = true;

    @Override
    public void run() {
        try {
            System.out.println("程序A正在运行");
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new RuntimeException("A报错了!");
    }
}

execute可以结合setUncaughtExceptionHandler来使用达到异步效果:

public class FutureDemo3 {
    public static void main(String[] args) {
        FutureRunnable3 runnable3 = new FutureRunnable3();
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d")
                .setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        System.out.println("子线程的异常被自定义UncaughtExceptionHandler处理了");
                        runnable3.flg = false;
                    }
                })
                .build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), threadFactory);
        threadPoolExecutor.execute(runnable3);
        while (true) {
            if (!runnable3.flg) {
                System.out.println("程序结束");
                threadPoolExecutor.shutdown();
                break;
            }
        }
    }
}

class FutureRunnable3 implements Runnable {
    volatile boolean flg = true;

    @Override
    public void run() {
        try {
            System.out.println("程序正在运行");
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new RuntimeException("报错了!");
    }
}

这里通过ThreadFactory来设置UncaughtExceptionHandler,在 threadPoolExecutor.execute(runnable3)后不用等待子线程执行结果,继续往下执行

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值