线程吞掉异常信息

前言

在阅读线程相关知识的时候发现线程吞掉异常的知识,故记录下来。

代码示例

ExecutorService executorService = Executors.newFixedThreadPool(1);
        try {
            executorService.submit(() -> {
                Object obj = null;
                System.out.println(obj.toString());
            });
        } catch (Exception e) {
            System.out.println("catch Exception");
            e.printStackTrace();
        }
        try {
            executorService.execute(() -> {
                Object obj = null;
                System.out.println(obj.toString());
            });
        } catch (Exception e) {
            System.out.println("catch Exception");
            e.printStackTrace();
        }

运行结果:

Exception in thread "pool-1-thread-1" java.lang.NullPointerException
    at my.jdk.concurrent.ThreadPoolTest.lambda$uncaughtExceptionTest$2(ThreadPoolTest.java:68)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

catch肯定不会获取到信息,同时submit报错信息也没有打印出来,只打印的execute报错信息

问题

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

原因

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

jvm会在线程即将死掉的时候捕获所有未捕获的异常进行处理。

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

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) {
                    //这里捕获了所有异常调用setException
                    result = null;
                    ran = false;
                    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实现

//这个方法就是这事线程状态为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);
    }

所有知道了如果想获取sumbit的异常信息需要调用get方法,如下:

@Test
    public void caughtSumbitException() {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future future = executorService.submit(() -> {
            Object obj = null;
            System.out.println(obj.toString());
        });
        try {
            future.get();
        } catch (Exception e) {
            System.out.println("catch NullPointException");
        }
    }

输出:

catch NullPointException
execute怎么输入logger日志

查看execute代码实现

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) {
                        //这里直接抛出所有异常
                        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);
        }
    }

这里抛出的异常在哪里处理呢?
接下来处理是交由jvm处理,由于本人对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 {
    
        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().uncaughtException(this, e);
    }

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

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, 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);
            }
        }
    }

获取默认处理器进行线程处理
默认处理器为空则在Sytem.err就行错误信息输出
到这里有两个方法可以实现用logger输出

  • thread定义uncaughtExceptionHandler
  • Thread定义defaultUncaughtExceptionHandler
    示例代码:
@Test
    public void setUncaughtExceptionHandler() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (;;) {
                throw new RuntimeException("soming Exception");
            }
        }, "t1");
        t1.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        t1.start();
        new Thread(() -> {
            for (;;) {
                throw new RuntimeException("soming Exception");
            }
        }, "t2").start();
        Thread.sleep(1000);
    }

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        private Thread.UncaughtExceptionHandler defaultHandler;

        public MyUncaughtExceptionHandler() {
            this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            logger.info("logger println exception info, threadName:{}", t.getName());
        }
    }

输出:

Exception in thread "t2" java.lang.RuntimeException: soming Exception
    at my.jdk.concurrent.ThreadTest.lambda$setUncaughtExceptionHandler$1(ThreadTest.java:27)
    at java.lang.Thread.run(Thread.java:745)
[no tracer] 2018-06-05 19:01:55.230 [t1] INFO  my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t1

可以看到setUncaughtExceptionHandler只作用与t1,t2还是system.err输出

@Test
    public void defaultUncaughtExceptionHandler() throws InterruptedException {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        new Thread(() -> {
            for (;;) {
                throw new RuntimeException("soming Exception");
            }
        }, "t1").start();
        new Thread(() -> {
            for (;;) {
                throw new RuntimeException("soming Exception");
            }
        }, "t2").start();
        Thread.sleep(1000);

    }
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        private Thread.UncaughtExceptionHandler defaultHandler;

        public MyUncaughtExceptionHandler() {
            this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            logger.info("logger println exception info, threadName:{}", t.getName());
        }
    }

输出:

[no tracer] 2018-06-05 19:03:37.292 [t2] INFO  my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t2
[no tracer] 2018-06-05 19:03:37.292 [t1] INFO  my.jdk.concurrent.ThreadTest - logger println exception info, threadName:t1

作用域为所有线程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 【JAVA 线程】 线程 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者...
    Rtia阅读 2,766评论 2 20