UncaughtExceptionHandler—處理Runnable線程內的非受檢異常

學過Java多線程的夥伴們都知道,Java入門的多線程方案就是Thread類和Runnable類。如下:


public class Demo {
    public static void main(String[] args) {
        fun1();
        fun2();
    }

    public static void fun1() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("fun1-thread");
            }
        };
        thread.start();
    }

    public static void fun2() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("fun2-thread");
            }
        };
        thread.start();
    }
}

這是Java的基礎內容,在此不再贅述。不知道的夥伴可以參見我之前的博客,今天我們要說的是線程內部的異常處理。

Java的異常非爲受檢異常和非受檢異常。由於在多線程中,run()方法無法繼續向上顯式拋出異常。所以這就使得Thread線程中的異常處理變得棘手。

首先對於受檢異常,其應對辦法非常通用:就是直接在run()方法體中捕獲異常,然後進行對應的處理。對於非受檢異常,JVM會幫助我們捕獲到。那麼我們如何處理JVM捕獲的異常呢?答案是Thread.UncaughtExceptionHandler類。正如JDK文檔所介紹的一樣:

當一個線程由於發生了非受檢異常而終止時,JVM會使用Thread.gerUncaughtExceptionHandler()方法查看該線程上的UncaughtExceptionHandler,並調用他的uncaughtException()方法”——翻譯自JDK7 API文檔。

下面我們來嘗試使用hread.UncaughtExceptionHandler類,來處理線程內部的非受檢異常(受檢異常直接在run()方法體內部的catch子句中處理)。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by yizhen on 2017/1/7.
 */
public class Demo {
    private static final AtomicInteger num = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        final Thread thread = new Thread(new ThreadTask());
        //此處使用thread.setUncaughtExceptionHandler(new ThreadExceptionHandler)可以代替ThreadTask中
        //的Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler(),但這樣不好
        thread.start();
    }

    private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("One uncaught exception was got:");
            System.out.println("Thread id=" + t.getId());
            System.out.println("Thread name=" + t.getName());
            e.printStackTrace(System.out);
            // 當捕獲到非受檢異常時,可以斷定原來的線程已經被JVM終止。
            // 此時可以新建一個線程繼續執行原來的任務
            if (num.get() < 5) {
                new Thread(new ThreadTask()).start();
            }
        }
    }

    private static final class ThreadTask implements Runnable {
        @Override
        public void run() {
            num.incrementAndGet();
            Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());//設置非受檢異常的ExceptionHandler
            try {
                for (int i = 4; i >= 0; i--) {
                    TimeUnit.SECONDS.sleep(1);
                    // 當i=0時候拋出的非受檢異常,將導致當前線程被JVM殺死
                    // 但異常會被在上面設置的ThreadExceptionHandler捕獲到,進而被處理
                    System.out.println(12 / i);
                }
            } catch (InterruptedException e) {//受檢異常直接在run方法體內部處理
                e.printStackTrace();
            }
        }
    }
}


上面的代碼不難理解,在主函數中,我們執行了一個Thread線程,這個線程中執行ThreadTask任務。在ThreadTask任務的實現中,如果是受檢異常,直接在run()方法體中處理。對於非受檢異常,我們在run()方法體的開頭設置了非受檢異常處理類ThreadExceptionException,這個類的uncaughtException()方法就是處理線程內部非捕獲異常的具體執行者。該方法會判斷任務執行的次數,如果沒有超過5次,就會創建一個Thread線程來重新執行任務(讀者要清楚,一旦一個線程拋出了非受檢異常,JVM就會把它殺死,然後把捕獲到的非受檢異常傳遞給UncaughtExceptionHandler類對象類處理)
一定有讀者會問,如果沒有通過setUncaughtExceptionHandler()方法設置Handler怎麼辦?這個問題在JDK API中給了回答:
“如果一個線程沒有顯式的設置它的UncaughtExceptionHandler,JVM就會檢查該線程所在的線程組是否設置了UncaughtExceptionHandler,如果已經設置,就是用該UncaughtExceptionHandler;否則查看是否在Thread層面通過靜態方法setDefaultUncaughtExceptionHandler()設置了UncaughtExceptionHandler,如果已經設置就是用該UncaughtExceptionHandler;如果上述都沒有找到,JVM會在對應的console中打印異常的堆棧信息。”——翻譯自JDK7 API文檔
/**
 * Created by yizhen on 2017/1/7.
 */
public class Demo {

    public static void main(String[] args) throws InterruptedException {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("static thread exception handler -- " + t.getName());
            }
        });
        final Thread t1 = new Thread(new ThreadTaskWithHandler(), "t1");
        t1.start();

        final Thread t2 = new Thread(new ThreadTaskNoHandler(), "t2");
        t2.start();

    }

    private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("explicit exception handler -- " + t.getName());
        }
    }

    private static final class ThreadTaskWithHandler implements Runnable {

        @Override
        public void run() {
            Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());
            System.out.println(12 / 0);
        }
    }

    private static final class ThreadTaskNoHandler implements Runnable {
        @Override
        public void run() {
            System.out.println(12 / 0);
        }
    }
}
從上面的程序可以看到,t1線程的非受檢異常始終會被explicit exception handler捕獲到,而t2線程的非受檢異常始終會被static thread exception handler捕獲到。
至此,Thread線程內的異常處理就介紹完了,這包括受檢異常和非受檢異常。細心的讀者會發現,本文僅僅涉及Thread和Runnable的多線程體系。在Java中,還有Executor和Callable的多線程體系。那關於這個體系的異常如何處理呢?筆者會後續博文中介紹!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章