美文网首页
线程吞掉异常信息

线程吞掉异常信息

作者: 言如止水 | 来源:发表于2018-06-05 19:04 被阅读0次

    前言

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

    代码示例

    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
    

    作用域为所有线程

    相关文章

      网友评论

          本文标题:线程吞掉异常信息

          本文链接:https://www.haomeiwen.com/subject/drmwrftx.html