線程吞掉異常信息

前言

在閱讀線程相關(guān)知識(shí)的時(shí)候發(fā)現(xiàn)線程吞掉異常的知識(shí),故記錄下來(lái)。

代碼示例

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();
        }

運(yùn)行結(jié)果:

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肯定不會(huì)獲取到信息,同時(shí)submit報(bào)錯(cuò)信息也沒(méi)有打印出來(lái),只打印的execute報(bào)錯(cuò)信息

問(wèn)題

  • 為什么不能拋出到外部線程捕獲
  • submit為什么不能打印報(bào)錯(cuò)信息
  • execute怎么使用logger打印報(bào)錯(cuò)新信

原因

為什么不能拋出到外部線程捕獲?

jvm會(huì)在線程即將死掉的時(shí)候捕獲所有未捕獲的異常進(jìn)行處理。

submit為什么不能打印報(bào)錯(cuò)信息

submit源碼實(shí)現(xiàn):

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);//創(chuàng)建FutureTask類(lèi)
        execute(ftask);
        return ftask;
    }

查看FutureTask.run方法實(shí)現(xiàn)

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) {
                    //這里捕獲了所有異常調(diào)用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實(shí)現(xiàn)

//這個(gè)方法就是這事線程狀態(tài)為completing -> exceptional
//同時(shí)用outcome保存異常信息。
protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

然后我們繼續(xù)追蹤outcome的使用

//report會(huì)拋出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會(huì)調(diào)用report方法
public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

所有知道了如果想獲取sumbit的異常信息需要調(diào)用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代碼實(shí)現(xiàn)

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);
        }
    }

這里拋出的異常在哪里處理呢?
接下來(lái)處理是交由jvm處理,由于本人對(duì)jvm源碼不了解,只知道jvm調(diào)用Thread. dispatchUncaughtException來(lái)處理所有未捕獲的異常

/**
     * 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);
    }

這里可以根據(jù)該方法注釋解釋?zhuān)馑季褪沁@個(gè)方法只用于JVM調(diào)用,處理線程未捕獲的異常。
繼續(xù)查看getUncaughtExceptionHandler()方法

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

    // 處理類(lèi)
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

    // 默認(rèn)處理類(lèi)
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    /**
    * 設(shè)置默認(rèn)的處理類(lèi),注意是靜態(tài)方法,作用域?yàn)樗芯€程設(shè)置默認(rèn)的處理類(lèi)
    **/
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(
                new RuntimePermission("setDefaultUncaughtExceptionHandler")
                    );
        }

         defaultUncaughtExceptionHandler = eh;
     }
    //獲取默認(rèn)處理類(lèi)
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    }
    //獲取處理類(lèi),注意不是靜態(tài)方法,只作用域該線程
    //處理類(lèi)為空使用ThreadGroup
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
    //設(shè)置處理類(lèi)
    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) {
        //獲取處理類(lèi)型進(jìn)行異常處理
        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);
            }
        }
    }

獲取默認(rèn)處理器進(jìn)行線程處理
默認(rèn)處理器為空則在Sytem.err就行錯(cuò)誤信息輸出
到這里有兩個(gè)方法可以實(shí)現(xiàn)用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

作用域?yàn)樗芯€程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評(píng)論 19 139
  • 【JAVA 線程】 線程 進(jìn)程:是一個(gè)正在執(zhí)行中的程序。每一個(gè)進(jìn)程執(zhí)行都有一個(gè)執(zhí)行順序。該順序是一個(gè)執(zhí)行路徑,或者...
    Rtia閱讀 2,893評(píng)論 2 20

友情鏈接更多精彩內(nèi)容