IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> java多线程(6):线程与线程池的异常处理机制 -> 正文阅读

[Java知识库]java多线程(6):线程与线程池的异常处理机制

前言

java提供了一套完善的异常处理机制,异常的基础本文不展开,本文重点讲述线程的异常处理。

正文

本文将线程的实现分为2种,一种是实现了Runnable接口的无返回值的线程,无法在主线程感知到子线程的异常,没有被捕获的异常只会输出到控制台,那么未被捕获的异常如果没有进行处理或者日志记录,就会造成异常信息丢失;另一种是实现了Callable接口的有返回值的线程,子线程的异常信息会通过FutureTask返回值传递给主线程。最后再说一下线程池的异常处理方式。

一、线程的异常处理方法

1、主线程无法捕获子线程的异常

示例代码:

public class RunNableTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        try {
            new Thread(new RunTask1()).start();
        } catch (Exception e) {
            System.out.println("catch excetion in main thread");
        }

    }
}

class RunTask1 implements Runnable {
    @Override
    public void run() {
        int a = 1 / 0; // 子线程抛异常
        System.out.println("threadpool test:" + Thread.currentThread().getName());
    }
}

输出:

main
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
	at org.jimmy.runnable.RunTask1.run(RunNableTest.java:18)
	at java.lang.Thread.run(Thread.java:748)

上述代码示例中,主线程无法捕获到子线程抛出的异常,异常在子线程的Thread.run()方法中抛出后,JVM会调用dispatchUncaughtException方法最终输出到控制台。

2、Runnable子线程自行处理异常

示例代码:

public class RunNableTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        new Thread(new RunTask1()).start();
    }
}

class RunTask1 implements Runnable {
    @Override
    public void run() {
        try {
            int a = 1 / 0; // 子线程抛异常,并捕获
        } catch (Exception e) {
            System.out.println("catch exception in child thread");
        }
        System.out.println("threadpool test:" + Thread.currentThread().getName());
    }
}

输出:

main
catch exception in child thread
threadpool test:Thread-0

上述示例代码展示了子线程内的异常处理方式,这是推荐的线程内异常处理方法。

3、主线程捕获子线程返回的FutureTask抛出的异常

示例代码:

public class RunNableTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        FutureTask<String> futureTask = new FutureTask<>(new RunTask2());
        // 启动Callable子线程
        new Thread(futureTask).start();
        
        try {
            // 获取返回值时,会同时捕获子线程抛出的异常
            String s = futureTask.get();
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class RunTask2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        int a = 1 / 0; // 抛异常
        return "hello";
    }
}

输出:

main
catch exception in main thread

主线程成功捕获了Callable子线程抛出的异常。

二、线程池的异常处理方式

首先需要说明一下:线程池提交任务的方式有2种:一种是execute()方法提交Runnable无返回值的任务,另一种是submit()方法提交Callable有返回值的任务。而使用submit()方法提交任务是有坑的,如果提交之后没有使用get方法获取返回值,异常信息就丢了。

1、使用execute方法提交任务不能捕获异常

示例代码:

public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 2L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(6));
        System.out.println(Thread.currentThread().getName());

        for (int i = 0; i < 2; i++) {
            threadPoolExecutor.execute(new RunTask());
        }
    }
}

class RunTask implements Runnable {
    @Override
    public void run() {
        int a = 1 / 0;
        System.out.println("threadpool test:" + Thread.currentThread().getName());
    }
}

输出:

main
Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at org.jimmy.threadPool.RunTask.run(ThreadPoolTest.java:24)
	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)
java.lang.ArithmeticException: / by zero
	at org.jimmy.threadPool.RunTask.run(ThreadPoolTest.java:24)
	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)

跟上面讲的一样,Runnable接口任务无法在主线程中捕获,异常会被JVM打印到控制台,需要在子线程中捕获异常。

2、使用submit方法提交Callable任务,并获取返回值

示例代码:

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 创建线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 2L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(6));
        // 打印当前线程
        System.out.println(Thread.currentThread().getName());

        List<Future<String>> futureList = new ArrayList<>();
        // submit提交任务
        for (int i = 0; i < 2; i++) {
            Future<String> future = threadPoolExecutor.submit(new RunTask2());
            futureList.add(future);
        }
        // 获取返回值,会获取线程池中抛出的异常
        for (Future<String> future : futureList) {
            try {
                System.out.println(future.get());
            } catch (InterruptedException | ExecutionException e) {
                System.out.println("catch exception in main thread");
            }
        }
    }
}

class RunTask2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        int a = 1 / 0; // 抛异常
        return "hello";
    }
}

输出:

main
catch exception in main thread
catch exception in main thread

上述代码可以看到,主线程在使用get方法获取返回值时,也捕获到了线程池中抛出的异常。

我们再来看下只submit提交任务,不get返回值的情况。

3、只submit提交任务,不get返回值

实例代码,注释掉一部分代码:

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 创建线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 2L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(6));
        // 打印当前线程
        System.out.println(Thread.currentThread().getName());

        List<Future<String>> futureList = new ArrayList<>();
        // submit提交任务
        for (int i = 0; i < 2; i++) {
            Future<String> future = threadPoolExecutor.submit(new RunTask2());
            futureList.add(future);
        }
        // // 获取返回值,会获取线程池中抛出的异常
        // for (Future<String> future : futureList) {
        //     try {
        //         System.out.println(future.get());
        //     } catch (InterruptedException | ExecutionException e) {
        //         System.out.println("catch exception in main thread");
        //     }
        // }
    }
}

class RunTask2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        int a = 1 / 0; // 抛异常
        return "hello";
    }
}

输出:

main

程序只打印了主线程信息,线程池中的子线程信息全都被隐藏了,生产环境就会有大问题。

总结

真正实践中一般使用线程池来处理多线程问题。如果无需返回值,则使用execute()方法提交任务,并在子线程中处理异常;如果需要返回值,则使用submit()方法提交任务,然后要么在子线程中处理异常,要么通过future.get()方法把异常抛给主线程。一定不要既不在子线程处理异常,又不通过future.get()方法把异常抛给主线程。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-22 14:00:25  更:2021-07-22 14:03:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/18 17:56:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码