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

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. 第一册:Lesson 123.

    原文:A trip to Australia. question:Who is the man with the beard? Look ,Scott.This is a photograph I t ...

  2. c# String ,String[] 和 List<String>之间的转换

    C#对字符串进行处理时,经常需要进行String,String[]和List<String>之间的转换 本文分析一下它们的差异和转换 一. 1. String > String[] ...

  3. Saslauthd服务实现SMTP发信认证

    一.SMTP发信认证 通过sasl库中的saslauthd服务实现SMTP认证 二.部署 1.先安装postifx 略 2.生成sasl配置文件,实现使用sasl认证 vim /usr/lib64/s ...

  4. Django之无名分组,有名分组

    在Django 2.0版本之前,在urls,py文件中,用url设定视图函数 urlpatterns = [ url(r'login/',views.login), ] 其中第一个参数是正则匹配,如下 ...

  5. [C#学习笔记3]关于Main(string[ ] args)中args命令行参数

    Main(string[] args)方法是C#程序的入口,程序从这里开始执行,在这里结束.C#代码逻辑要包含在一个类型(Type)中,游离的.全局的变量或函数是不存在的,这里的类型包括类(class ...

  6. [20190418]exclusive latch spin count.txt

    [20190418]exclusive latch spin count.txt--//昨天测试"process allocation" latch,主要这个latch与其它拴锁s ...

  7. Docker-Docker-compose应用

    Docker-compose是用来定义和运行多容器应用的工具,它是独立于docker存在的,需要单独安装.实际应用场景中,我们的应用可能被打包运行在不同的容器里面,例如一个常规的web应用可能会涉及到 ...

  8. REST教程

    REST教程 越来越多的人开始意识到,网站即软件,而且是一种新型的软件.这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high late ...

  9. Asp.Net WebAPI配置接口返回数据类型为Json格式

    Asp.Net WebAPI配置接口返回数据类型为Json格式   一.默认情况下WebApi 对于没有指定请求数据类型类型的请求,返回数据类型为Xml格式 例如:从浏览器直接输入地址,或者默认的XM ...

  10. Windows 10 远程连接出现函数错误 【这可能由于CredSSP加密Oracle修正】

    win+R 弹出窗口输入gpedit.msc 计算机配置=>管理模板=>系统=>凭据分配=>加密Oracle修正 编辑策略设置=>已启用=>保护级别=>易受攻 ...