为了方便遍描述问题,如下是简化后的

public class RunException {
    public static void main(String[] args) {

        ExecutorService readerPool = Executors.newFixedThreadPool(3);
        readerPool.submit(new Runnable() {
            public void run() {
               throw new RuntimeException("异常");
            }
        });

        readerPool.shutdown();
    }
}

此处FixedThreadPool吞掉了异常。

问题

  1. 为什么不能抛出到外部线程捕获
  2. submit为什么不能打印报错信息
  3. execute怎么使用logger打印报错信息

为什么不能抛出到外部线程捕获

jvm会在线程即将死掉的时候捕获所有未捕获的异常进行处理。默认使用的是Thread.defaultUncaughtExceptionHandler

submit为什么不能打印报错信息

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);//创建FutureTask类
        execute(ftask);
        return ftask;
    }

查看FutureTask.run():

   public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    //这里捕获了所有异常调用setException
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

接着查看setException(ex);,将线程状态由completing改为exceptional,并将异常信息存在outcome中:

    //这个方法就是这事线程状态为completing -> exceptional
    //同时用outcome保存异常信息。
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

继续查看outcome的使用:

//report会抛出exception信息
private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

//get会调用report()方法
public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
  1. report会抛出exception信息,但report是私有方法;
  2. get会调用report()方法

所以如果需要获取异常信息就需要调用get()方法。

execute怎么输入logger日志

查看execute的实现ThreadPoolExecutor.execute()

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

从代码可知,线程池将任务加入了任务队列,需要看看线程在哪执行任务的。那么只需要看看有没有获取任务的函数,ThreadPoolExecutor.getTask()即是获取任务的函数,通过查找,ThreadPoolExecutor.runWorker调用了ThreadPoolExecutor.getTask(),它应该是执行任务的代码:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                         //这里直接抛出所有Runtime异常
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

代码注释中看到获取RuntimeException的位置了。

这里抛出的异常在哪里处理呢? 接下来处理是交由jvm处理,从已经学习的知识中只知道jvm调用Thread.dispatchUncaughtException来处理所有未捕获的异常

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

这里可以根据该方法注释解释,意思就是这个方法只用于JVM调用,处理线程未捕获的异常。 继续查看getUncaughtExceptionHandler()方法:

    public interface UncaughtExceptionHandler {s
        void uncaughtException(Thread t, Throwable e);
    }

    // 处理类
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

    // 默认处理类
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    /**
    * 设置默认的处理类,注意是静态方法,作用域为所有线程设置默认的处理类
    **/
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(
                new RuntimePermission("setDefaultUncaughtExceptionHandler")
                    );
        }

         defaultUncaughtExceptionHandler = eh;
     }
    //获取默认处理类
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    }
    //获取处理类,注意不是静态方法,只作用域该线程
    //处理类为空使用ThreadGroup
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
    //设置处理类
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        //获取处理类型进行异常处理
        getUncaughtExceptionHandler(www.mumingyue.cn).uncaughtException(this, e);
    }

如果线程UncaughtExceptionHandler处理器为空则threadGroup处理器 查看threadGroup:

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t,www.douniu2.cc e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

从代码中可以看出,

  1. 如果父进程不为空,则使用父进程处理未捕获异常;
  2. 如果无父进程,则获取默认的UncaughtExceptionHandler进行处理。
    1. 默认的UncaughtExceptionHandler为null,则使用Sytem.err将错误信息输出;
    2. 默认的UncaughtExceptionHandler不为null,则使用UncaughtExceptionHandler进行处理。

所以有两个方法实现用logger输出:

  1. Thread定义uncaughtExceptionHandlerThread.setUncaughtExceptionHandler(www.tianscpt.com),该方法仅能设置某个线程的默认UncaughtExceptionHandler
  2. Thread定义defaultUncaughtExceptionHandler:使用Thread.setDefaultUncaughtExceptionHandler,该方法设置所有线程的默认UncaughtExceptionHandler

测试程序

仅某个线程设置默认UncaughtExceptionHandler

public static void oneThreadUncaughtExceptionHandler() {
        Thread t1 = new Thread((www.mhylpt.com/) -> {
            throw new RuntimeException(" t1 runtime exception");
        }, "t1");
        t1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(Thread.currentThread(www.baihuiyulep.cn) + "trigger uncaugh exception handler");
            }
        });

        t1.start();
        Thread t2 = new Thread(() -> {
            throw new RuntimeException(" t2 runtime exception");
        }, "t2");
        t2.start();
    }

设置defaultUncaughtExceptionHandler

public static void defaultThreadUncaughtExceptionHandler() {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(www.ysyl157.com Thread t, Throwable e) {
                System.out.println(Thread.currentThread() + "trigger uncaugh exception handler");
            }
        });
        new Thread(() -> {
            throw new RuntimeException(www.tianjiuyule178.com  " t1 runtime exception");
        }, "t1").start();
        new Thread(() -> {
            throw new RuntimeException(" t2 runtime exception");
        }, "t2").start();
    }

解惑

那为什么我们的例子代码中,异常不会输出呢?应该有兜底的System.err来输出异常才对。 不是这样的,我们的例子中的异常实际上是处理了的,它捕获了异常,并且保存到了outcome中。仅仅有未捕获的异常,JVM才会调用Thread.dispatchUncaughtException来处理。

FixedThreadPool吞掉了异常的更多相关文章

  1. [Net 6 AspNetCore Bug] 解决返回IAsyncEnumerable<T>类型时抛出的OperationCanceledException会被AspNetCore 框架吞掉的Bug

    记录一个我认为是Net6 Aspnetcore 框架的一个Bug Bug描述 在 Net6 的apsnecore项目中, 如果我们(满足以下所有条件) api的返回类型是IAsyncEnumerabl ...

  2. 一个问题:关于finally中return吞掉catch块中抛出的异常

    今天遇到一个感觉很神奇的问题,记录一下问题以及自己分析问题的思路. 预警:不知道怎么看java字节码的朋友可能需要先看一下如何阅读java字节码才能看懂后面的解释. 我有一段程序: public cl ...

  3. 读书笔记 effective c++ Item 8 不要让异常(exceptions)离开析构函数

    1.为什么c++不喜欢析构函数抛出异常 C++并没有禁止析构函数出现异常,但是它肯定不鼓励这么做.这是有原因的,考虑下面的代码: class Widget { public: ... ~Widget( ...

  4. 唯品会Java开发手册》1.0.2版阅读

    <唯品会Java开发手册>1.0.2版阅读 1. 概述 <阿里巴巴Java开发手册>,是首个对外公布的企业级Java开发手册,对整个业界都有重要的意义. 我们结合唯品会的内部经 ...

  5. python基础-面向对象进阶

    一.什么是反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它首先被 ...

  6. Python之路【第六篇】python基础 之面向对象进阶

    一 isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象  和  issubclass(su ...

  7. 分享总结:更好地CodeReview

            代码质量分享    2016_06_24_舒琴_代码质量.key    For 代码提交人     基本原则 Review时机: 对于普通bugfix或优化,CodeReview最迟要 ...

  8. J2EE开发规范

    J2EE开发规范一 JAVA编码规范1 命名规范1.1 包命名 包名称必须全部用小写. 命名方式:业务领域名.公司名.项目名.模块名 如com.yr.xxx.dao.1.2 类命名类名以英文单词取 ...

  9. Python系列之 - 上下文管理协议

    with obj as f: '代码块' 1.with obj ---->触发obj.__enter__(),拿到返回值 2.as f----->f=返回值. 3.with obj as ...

随机推荐

  1. .net core jwt 入门记录

    从百度里搜索里搜索了很多jwt的文章,跟着文章写了一个demo,这里记录下学习过程中碰上的问题.看文章多遍,不如手工实现一次. 模板已上传到github.com:dogvane/webapi_jwt_ ...

  2. 【开源分享】2018CRM C# 源码(基于小黄豆CRMv2.0.925.3版本功能更新)

    分享出来的初衷,我分享一下最近我在小黄豆CRM2.0版本(小黄豆CRM+v2.0.925.3)上加的功能,如果有类似需求的,可以把功能代码发你,节约你的开发时间.(这是在小黄豆开源免费CRM①群231 ...

  3. I/O输出流基础之FileOutputStream

    OutputStream:是所有字节输出流的父类,其作用是:用这个流把网络数据(getOutputStream()),或者内存中的字节数组数据写到文件系统中文件系统(FileOutputStream) ...

  4. Springboot2注解使用Mybatis动态SQL

    1.简单SQL使用 //从***数据表获取统计数据 @Select("select count(*) from issues where issue_type = #{type}" ...

  5. 使用 MSIX 打包 DotNetCore 3.0 客户端程序

    如何你希望你的 WPF 程序能够以 Windows 的保护机制保护起来,不被轻易反编译的话,那么这篇文章应该能帮到你. 介绍 MSIX 是微软于去年的 Windows 开发者日峰会 上推出的全新应用打 ...

  6. 洛谷P3366 【模板】最小生成树(Boruvka算法)

    题意 题目链接 Sol 自己yy着写了一下Boruvka算法. 算法思想很简单,就是每次贪心的用两个联通块之间最小的边去合并. 复杂度\(O(n \log n)\),然鹅没有Kruskal跑的快,但是 ...

  7. PostGIS计算矢量切片(二)--按值渲染

    方案背景     今年三月份写了一篇postgis计算矢量切片,参考了网上资料给出了一份很粗糙的相关方案(文章写的更粗糙).当时的方案中只能针对gis形状进行渲染,而不能用属性渲染.针对这个情况,本文 ...

  8. Dynamics 365-关于Solution的那些事(三)

    这一篇的内容,是关于Solution的使用建议的,如果大家有什么实用的建议,欢迎留言讨论. 一. 版本控制 Solution是有版本号的,率性的人可能在新建一个solution的时候,直接赋值1.0, ...

  9. 四、Snapman多人协作电子表格之——Exprtk脚本

    Snapman多人协作电子表格是一个即时工作系统. Snapman中嵌入了Exprtk脚本进行公式数据运算.Exprtk是一种高性能的脚本,经测试它的数据运算性能只比C#和java底20%. 一.Ex ...

  10. 数据结构:关键路径,利用DFS遍历每一条关键路径JAVA语言实现

    这是我们学校做的数据结构课设,要求分别输出关键路径,我查遍资料java版的只能找到关键路径,但是无法分别输出关键路径 c++有可以分别输出的,所以在明白思想后自己写了一个java版的 函数带有输入函数 ...