多线程核心(2):线程异常处理

多线程 专栏收录该内容
31 篇文章 5 订阅

1 子线程异常主线程无法感知

  • 主线程可以轻松发现异常,子线程却不行 
  • 子线程异常无法用传统方法捕获
public class NoCaughtThread {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println(3 / 2);
            System.out.println(3 / 0);
            System.out.println(3 / 1);
        };
        
        try {
            Thread thread = new Thread(task, "AAA");
            thread.start();
        } catch (Exception e) { // 抓不到
            System.out.println("errMsg=" + e.getMessage());
        }
    }
}
/**
 * 单线程,抛出,处理,有异常堆栈 多线程,子线程发生异常,会有什么不同?
 */
public class ExceptionInChildThread {
    public static void main(String[] args) {
        new Thread(() -> {
            throw new RuntimeException("子线程出错啦...");
        }).start();
        //依然还在执行
        for (int i = 0; i < 6; i++) {
            System.out.println(i);
        }
    }
}
/**
 * 1. 不加try catch抛出4个异常,都带线程名字
 * 2. 加了try catch,期望捕获到第一个线程的异常,线程234不应该运行,希望看到打印出Caught Exception
 * 3. 执行时发现,根本没有Caught Exception,线程234依然运行并且抛出异常
 * 
 * 说明线程的异常不能用传统方法捕获
 */
public class CantCatchDirectly implements Runnable {
    @Override
    public void run() {
        throw new RuntimeException();
    }
    
    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 (RuntimeException e) {
            System.out.println("Caught Exception.");
        }
    }
}

2 如何处理异常

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

public class CantCatchDirectly implements Runnable {
    @Override
    public void run() {
        try {
            throw new RuntimeException();
        } catch (RuntimeException e) {
            System.out.println("Caught Exception.");
        }
    }
    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();
    }
}

方案二(推荐):利用 UncaughtExceptionHandler

//java.lang.Thread.UncaughtExceptionHandler
@FunctionalInterface
public interface UncaughtExceptionHandler {
	void uncaughtException(Thread t, Throwable e);
}

 

自己实现

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

给程序统一设置

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 自己的MyUncaughtExceptionHanlder
 */
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());
        System.out.println(name + "捕获了异常" + t.getName() + "异常");
    }
}
/**
 * 使用刚才自己写的UncaughtExceptionHandler
 */
public class UseOwnUncaughtExceptionHandler {
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
            throw new RuntimeException();
        };
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1"));
        new Thread(run, "MyThread-1").start();
        Thread.sleep(300);
        new Thread(run, "MyThread-2").start();
        Thread.sleep(300);
        new Thread(run, "MyThread-3").start();
        Thread.sleep(300);
        new Thread(run, "MyThread-4").start();
    }
}

给每个线程单独设置

/**
 * 异常处理
 */
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // 记录日志等
        System.out.println("thread=" + t.getName() + ",exception=" + e.getClass().getName() + ",errMsg=" + e.getMessage());
    }
}
public class UseOwnUncaughtExceptionHandler {
    public static void main(String[] args) {
        Runnable run = () -> {
            //throw new IllegalArgumentException("参数个数少于预期");
            throw new ArithmeticException("分母不能为零");
        };
        Thread task = new Thread(run, "AAA");
        // 局部和全局都设置了,只有局部生效
        // 设置多个局部的,后面的会覆盖前面的
        task.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        task.start();
    }
}

线程池设置

/**
 * 异常处理
 */
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // 记录日志等
        System.out.println("thread=" + t.getName() + ",exception=" + e.getClass().getName() + ",errMsg=" + e.getMessage());
    }
}

如果采用线程池通过execute的方法去捕获异常,需要将异常的捕获封装到Runnable或者Callable中

// java.util.concurrent.Executor#execute
void execute(Runnable command);

thread.setUncaughtExceptionHandler 未捕获到异常 

// 不能捕获到异常
ExecutorService exec = Executors.newCachedThreadPool();
Thread task = new Thread(() -> {
    System.out.println(3 / 2);
    System.out.println(3 / 0);
    System.out.println(3 / 1);
});
// 无效的设置
task.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
exec.execute(task);
exec.shutdown();

//1
//Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
//	at UseOwnUncaughtExceptionHandler.lambda$main$0(UseOwnUncaughtExceptionHandler.java:10)
//	at java.base/java.lang.Thread.run(Thread.java:830)
//	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
//	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
//	at java.base/java.lang.Thread.run(Thread.java:830)

将异常的捕获封装到Runnable或者Callable中,就能捕获到异常

// 将异常的捕获封装到Runnable或者Callable中,就能捕获到异常
ExecutorService exec = Executors.newCachedThreadPool();
Runnable task = () -> {
    Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
    System.out.println(3 / 2);
    System.out.println(3 / 0);
    System.out.println(3 / 1);
};
exec.execute(task);
exec.shutdown();

//1
//thread=pool-1-thread-1,exception=java.lang.ArithmeticException,errMsg=/ by zero

只有通过execute提交的任务,才能将它抛出的异常交给UncaughtExceptionHandler,而通过submit提交的任务,无论是抛出的未检测异常还是已检查异常,都将被认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。
 

ExecutorService exec = Executors.newCachedThreadPool();
Runnable task = () -> {
    System.out.println(3 / 2);
    System.out.println(3 / 0);
    System.out.println(3 / 1);
};
Future<?> res = exec.submit(task);
exec.shutdown();

// 1
ExecutorService exec = Executors.newCachedThreadPool();
Runnable task = () -> {
    Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
    System.out.println(3 / 2);
    System.out.println(3 / 0);
    System.out.println(3 / 1);
};
Future<?> res = exec.submit(task);
exec.shutdown();

//1

future.get() 会抛出异常 

ExecutorService exec = Executors.newCachedThreadPool();
Runnable task = () -> {
    Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
    System.out.println(3 / 2);
    System.out.println(3 / 0);
    System.out.println(3 / 1);
};
Future<?> future = exec.submit(task);
exec.shutdown();
try {
    future.get();
} catch (InterruptedException | ExecutionException e) {
    System.out.println("errMsg=" + e.getMessage());
}

//1
//errMsg=java.lang.ArithmeticException: / by zero

https://blog.csdn.net/u013256816/article/details/50417822

 

  • 5
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

<p><strong><span style="color: #337fe5;">[JAVA工程师必会知识点之并发编程]</span></strong></p> <p style="font-size: 14px; background-color: #ffffff;"> </p> <p style="color: #008781;"> </p> <p class="ql-long-24357476" style="color: #222226; font-family: "font-size:14px; background-color: #ffffff;"> </p> <p class="MsoNormal"><strong><span style="color: #000000;">1、现在几乎</span><span style="color: #000000;">100%</span><span style="color: #000000;">的公司面试都必须面试并发编程,尤其是互联网公司,对于并发编程的要求更高,并发编程能力已经成为职场敲门砖。</span></strong></p> <p class="MsoNormal"><strong><span style="color: #000000;">2</span><span style="color: #000000;">、现在已经是移动互联和大数据时代,对于应用程序的性能、处理能力、处理时效性要求更高了,传统的串行化编程无法充分利用现有的服务器性能。</span></strong></p> <p class="MsoNormal"><strong><span style="color: #000000;">3</span><span style="color: #000000;">、并发编程是几乎所有框架的底层基础,掌握好并发编程更有利于我们学习各种框架。想要让自己的程序执行、接口响应、批处理效率更高,必须使用并发编程。</span></strong></p> <p class="MsoNormal"><strong><span style="color: #000000;">4、并发编程是中高级程序员的标配,是拿高薪的必备条件。</span></strong></p> <p> </p> <p><span style="color: #337fe5;">【主讲讲师】</span></p> <p><span style="color: #337fe5;"><span style="color: #000000;">尹洪亮Kevin:</span><br /><span style="color: #000000;">现任职某互联网公司首席架构师,负责系统架构、项目群管理、产品研发工作。</span><br /><span style="color: #000000;">10余年软件行业经验,具有数百个线上项目实战经验。</span><br /><span style="color: #000000;">擅长JAVA技术栈、高并发高可用伸缩式微服务架构、DevOps。</span><br /><span style="color: #000000;">主导研发的蜂巢微服务架构已经成功支撑数百个微服务稳定运行</span><br /><br /></span></p> <p style="color: #008781;"><strong><span style="color: #337fe5;">【推荐你学习这门课的理由:</span><span style="color: #e53333;">知识体系完整+丰富学习资料】</span></strong></p> <p style="margin-left: 18pt; text-indent: -18pt; background: white;">1<span style="color: #000000;">、 本课程总计122课时,由五大体系组成,目的是让你一次性搞定并发编程。分别是并发编程基础、进阶、精通篇、Disruptor高并发框架、RateLimiter高并发访问限流吗,BAT员工也在学。</span></p> <p style="color: #008781; background: white;"><span style="color: #000000;">2、课程附带附带3个项目源码,几百个课程示例,5个高清PDF课件。</span></p> <p style="color: #008781; background: white;"><span style="color: #000000;">3、本课程0基础入门,从进程、线程、JVM开始讲起,每一个章节只专注于一个知识点,每个章节均有代码实例。</span></p> <p style="color: #008781; background: white;"><span style="color: #000000;"> </span></p> <p style="color: #008781; background: white;"><span style="color: #337fe5;">【课程分为基础篇、进阶篇、高级篇】</span></p> <p style="color: #008781; background: white;"><span style="color: #337fe5;">一、基础篇<br /></span></p> <p style="color: #008781; background: white;"><span style="color: #000000;">基础篇从进程与线程、内存、CPU时间片轮训讲起,包含线程的3种创建方法、可视化观察线程、join、sleep、yield、interrupt,Synchronized、重入锁、对象锁、类锁、wait、notify、线程上下文切换、守护线程、阻塞式安全队列等内容。</span></p> <p style="color: #008781; background: white;"><span style="color: #337fe5;">二、进阶篇</span></p> <p style="color: #008781; background: white;"><span style="color: #000000;">进阶篇课程涵盖volatied关键字、Actomic类、可见性、原子性、ThreadLocal、Unsafe底层、同步类容器、并发类容器、5种并发队列、COW容器、InheritableThreadLocal源码解析等内容。</span></p> <p style="color: #008781; background: white;"><span style="color: #337fe5;">三、精通篇</span></p> <p style="color: #008781; background: white;"><span style="color: #000000;">精通篇课程涵盖JUC下的核心工具类,CountDownLath、CyclicBarrier、Phaser、Semaphore、Exchanger、ReentrantLock、ReentrantReadWriteLock、StampedLock、LockSupport、AQS底层、悲观锁、乐观锁、自旋锁、公平锁、非公平锁、排它锁、共享锁、重入锁、线程池、CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor、自定义线程池、ThreadFactory、线程池切面编程、线程池动态管理等内容,高并发设计模式,Future模式、Master Worker模式、CompletionService、ForkJoin等</span></p> <p style="color: #008781; background: white;"><span style="color: #337fe5;">课程中还包含</span></p> <p style="color: #008781; background: white;"><span style="color: #000000;">Disruptor高并发无锁框架讲解:Disruptor支持每秒600万订单处理的恐怖能力。深入到底层原理和开发模式,让你又懂又会用。</span></p> <p style="color: #008781; background: white;"><span style="color: #000000;">高并发访问限流讲解:涵盖木桶算法、令牌桶算法、Google RateLimiter限流开发、Apache JMeter压力测试实战。</span></p> <p> </p> <p class="ql-long-24357476" style="color: #222226; font-family: "font-size:14px; background-color: #ffffff;"><strong><span style="color: #337fe5;">【学完后我将达到什么水平?】</span></strong></p> <p class="ql-long-24357476" style="color: #222226; font-family: "font-size:14px; background-color: #ffffff;"><span style="color: #000000; font-family: ";">1、 吊打一切并发编程相关的笔试题、面试题。</span></p> <p class="ql-long-24357476" style="color: #222226; font-family: "font-size:14px; background-color: #ffffff;"><span style="color: #000000; font-family: ";">2、 重构自己并发编程的体系知识,不再谈并发色变。</span></p> <p class="ql-long-24357476" style="color: #222226; font-family: "font-size:14px; background-color: #ffffff;"><span style="color: #000000; font-family: ";">3、 精准掌握</span><span style="color: #000000; font-family: ";">JAVA</span><span style="color: #000000; font-family: ";">各种并发工具类、方法、关键字的原理和使用。</span></p> <p class="ql-long-24357476" style="color: #222226; font-family: "font-size:14px; background-color: #ffffff;"><span style="color: #000000; font-family: ";">4、 轻松上手写出更高效、更优雅的并发程序,在工作中能够提出更多的解决方案。</span></p> <p class="MsoNoSpacing" style="margin-left: 18pt; text-indent: -18pt;"><span style="color: #000000;"> </span></p> <p class="MsoListParagraph" style="margin-left: 18.0pt; text-indent: -18.0pt;"> </p> <p> </p> <p class="ql-long-24357476" style="color: #008781;"> </p> <p class="MsoListParagraph" style="color: #008781; margin-left: 36pt; text-indent: -36pt;" align="left"><strong><span style="color: #337fe5;">【</span><span style="color: #337fe5;">面向人群</span><span style="color: #337fe5;">】</span></strong></p> <p class="MsoListParagraph" style="color: #008781; margin-left: 36pt; text-indent: -36pt;" align="left"><span style="color: #000000;">1、 总感觉并发编程很难、很复杂、不敢学习的人群。</span></p> <p class="MsoListParagraph" style="color: #008781; margin-left: 36pt; text-indent: -36pt;" align="left"><span style="color: #000000;">2、 准备跳槽、找工作、拿高薪的程序员。</span></p> <p class="MsoListParagraph" style="color: #008781; margin-left: 36pt; text-indent: -36pt;" align="left"><span style="color: #000000;">3、 希望提高自己的编程能力,开发出更高效、性能更强劲系统的人群。</span></p> <p class="MsoListParagraph" style="color: #008781; margin-left: 36pt; text-indent: -36pt;" align="left"><span style="color: #000000;">4、 想要快速、系统化、精准掌握并发编程的人群。</span></p> <p class="MsoListParagraph" style="color: #008781; margin-left: 18pt; text-indent: -18pt;"><strong>【课程知识体系图】</strong></p> <p class="MsoListParagraph" style="color: #008781; margin-left: 18pt; text-indent: -18pt;"><img src="https://img-bss.csdnimg.cn/202007100721287398.png" alt="" /></p>
©️2021 CSDN 皮肤主题: 猿与汪的秘密 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值