子線程的異常處理 Thread.UncaughtExceptionHandler

這是Thread類的一個內部類,它是一個接口java

相關內容

實現類:git

  • ThreadGroup

概述

當線程由於一個沒有catch到的異常而終止時,能夠經過這個接口的實現類處理一些後續工做。
當線程由於異常終止時,JVM會查詢這個線程的UncaughtExceptionHandler對象,而且調用handler's uncaughtException的方法,將thread對象和exception異常對象做爲參數傳遞進去,若是Thread沒有顯示的setUncaughtExceptionHandler,那麼這個線程的ThreadGroup會作爲它的UncaughtExceptionHandler。若是你用的是默認的ThreadGroup,那麼ThreadGroup的uncaughtException其實調用了thread.getDefaultUncaughtExceptionHandler,最終執行的uncaughtException角色是默認的ExceptionHandler。github

方法

void uncaughtException(Thread t,Throwable e)

若是線程因爲異常而終止,此方法就會被JVM調用。此方法內部拋出的任何異常都會被系統忽略。這個方法是子線程執行任務出錯後的救命稻草。數據庫

Parameters:
t - the thread
e - the exceptionbash

代碼示例

場景

爲了演示這個Handler的用法,咱們能夠思考一下這個Handler的使用場景。異步

例如,每一個業務系統通常都會包含一個登錄服務,登錄服務大可能是一個獨立的微服務。通常登陸接口中都會作一些輔助操做,如記錄登陸日誌等等,並且咱們但願這類輔助操做在出現異常的狀況下,不會阻斷登陸主業務接口的正常返回ide

一個比較常規的作法是使用消息中間件將登陸成功的用戶信息封裝成消息發送至遠程服務異步處理。可是對於一箇中小規模的業務系統,你引入了消息中間件,也就是MQ的話,會帶來不小的維護成本,俗稱高射炮打蚊子。還有一種比較通用的辦法是將登陸成功的日誌消息放入一個Java阻塞隊列,而後使用一個線程池分配有限的資源去慢慢地將日誌信息寫入數據庫,這樣登陸請求正常返回,記錄日誌讓線程池異步的去處理,這樣你沒有引入第三方的中間件,維護成本也不高,你只要學好Java就好了。微服務

這種方式雖然很方便,可是其實存在問題:測試

寫入數據庫異常會致使丟消息

在進行數據庫插入操做時,多是數據庫鏈接池資源不夠,獲取鏈接超時會拋出異常,此時執行這個任務的Thread就會終止,這條消息就等於丟掉了,致使你的登陸日誌記錄不許確。爲了不這個問題,咱們可使用Thread.UncaughtExceptionHandler在拋出異常後將日誌記錄再次放回阻塞隊列ui

若是數據庫鏈接池資源緊張的問題只是瞬時的,那這樣ok,不然,仍是會存在問題:

若是數據庫鏈接池資源長時間緊張,線程入庫持續拋異常、不停的將任務放回隊列,並且登錄接口壓力依然很高,致使隊列消費速度跟不上隊列增加速度,那麼:

若是使用有界隊列作日誌記錄緩衝,隊列會滿,日誌記錄沒法放入隊列

若是使用無界隊列作日誌記錄緩衝,隊列可能數據量過大致使內存被撐爆

那麼咱們該怎麼辦呢?
這個其實仍是要看具體的需求,若是詳細的登陸日誌對你來講沒有那麼重要,那麼你能夠經過Thread.UncaughtExceptionHandler把登陸日誌放入另外一個隊列,而後按期對數據進行歸併,由於登陸次數這種數據每一個系統幾乎都要統計的。
若是你不光要統計登陸人數,具體誰登陸了你也要知道,那你能夠提早準備一個備用的數據庫,在主數據庫壓力拉滿的狀況下將數據放入另外一個隊列寫入備用數據庫中。

其實Thread.UncaughtExceptionHandler並非處理這種狀況的最佳辦法,並且JDK不推薦咱們顯示的建立線程,多數狀況下最好使用線程池。不過本文的重點是介紹Thread.UncaughtExceptionHandler,而且加深記憶。

另外,這種基於JDK內存隊列的異步模式在服務掛掉的時候會致使消息大面積丟失,若是讀者真的會遇到此類場景,並且你的用戶量每日都有大量增加,建議直接使用MQ。只有用戶量穩定,並且你能保證系統壓力峯值絕對不會超過某個預估值時才推薦使用內存隊列。

相關代碼

咱們來寫寫代碼實現上述上述場景

首先,咱們先建立一個實體類,它表明登陸日誌

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 登陸日誌對象,用於記錄用戶登陸信息
 * @author zhuzh
 * @date 2019.10.17
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginLogPO {
    private int id;
    private String username;
    private String createTime;
}
複製代碼

有了實體類,繼續寫DAO層的接口

/**
 * 登陸日誌持久層
 * @author zhuzh
 * @date 2019.10.17
 */
public interface LoginLoggingDAO {
    /**
     * 記錄登陸日誌到數據庫
     * @param po
     * @return
     */
    boolean log(LoginLogPO po);

    /**
     * 記錄日誌失敗
     * @return
     */
    void logFailed();

    /**
     * 獲取備用數據源中的日誌數據量
     * @return
     */
    int getBackupDatasourceSize();
}
複製代碼

DAO層的模擬實現類,咱們不會真的去插入數據庫,而是將數據放入一個隊列

/**
 * 一個登陸日誌持久層的模擬實現類
 * @author zhuzh
 * @date 2019.10.17
 */
public class MockLoginLoggingDAOImpl implements LoginLoggingDAO {
    /**
     * 使用定長隊列來模擬數據庫鏈接池資源緊張的場景
     * MAIN_DATASOURCE表明主數據源
     * 建立一個長度爲5的隊列,隊列滿了以後,向隊列插入數據會阻塞,直到隊列有空閒位置纔可插入。
     */
    private static final ArrayBlockingQueue<LoginLogPO> MAIN_DATASOURCE = new ArrayBlockingQueue<>(5);

    /**
     * 類初始化時直接將MAIN_DATASOURCE塞滿,這樣線程再插入數據時就會拋出異常
     */
    static {
        System.out.println("第三步:將主數據源塞滿,模擬主數據源鏈接池資源不足");
        for (int i = 0; i < 5; i++) {
            MAIN_DATASOURCE.offer(new LoginLogPO(i,"用戶"+i,"2019-10-1"+i));
        }
    }


    /**
     * BACKUP_DATASOURCE表明備用數據源,主數據源不可用時程序會切換到備用數據源,長度爲100
     */
    private static final ArrayBlockingQueue<LoginLogPO> BACKUP_DATASOURCE = new ArrayBlockingQueue<>(100);

    private static final AtomicInteger FAILED_COUNTER= new AtomicInteger();
    private static AtomicBoolean USE_BACKUP = new AtomicBoolean();


    @Override
    public boolean log(LoginLogPO po) {
        ArrayBlockingQueue<LoginLogPO> currentDatasource = getDatasource();
        try {
            boolean success = currentDatasource.offer(po,3, TimeUnit.SECONDS);
            if (!success){
                throw new DatasourceBusyException("主數據源繁忙,即將切換備用數據源!");
            }
            System.out.println("第五步:入庫成功,入庫成功的數據源爲,currentDatasource="+(currentDatasource==BACKUP_DATASOURCE?"BACKUP_DATASOURCE":"MAIN_DATASOURCE"));
        }catch (InterruptedException e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public void logFailed() {
        //記錄日誌的失敗次數+1
        int currentFailedCount = FAILED_COUNTER.incrementAndGet();
        //若是已經失敗10次了,說明如今主數據源狀態不是很理想啊
        if (currentFailedCount>=10&&!USE_BACKUP.get()){
            //就啓用備用數據源
            USE_BACKUP.compareAndSet(false,true);
            System.out.println("主數據源繁忙,已切換數據源爲備數據源,當前記錄失敗次數="+currentFailedCount+",備用數據源標誌位="+USE_BACKUP.get()+",將使用備用數據源");
        }
    }

    /**
     * 獲取數據源
     * @return
     */
    private ArrayBlockingQueue<LoginLogPO> getDatasource(){
        //若是主數據源隊列未滿,說明主數據庫鏈接池資源充足,能夠切回主數據源
        if (MAIN_DATASOURCE.isEmpty()){
            FAILED_COUNTER.set(0);
            USE_BACKUP.compareAndSet(true,false);
        }

        if (USE_BACKUP.get()){
            return BACKUP_DATASOURCE;
        }
        return MAIN_DATASOURCE;
    }

    @Override
    public int getBackupDatasourceSize() {
        return BACKUP_DATASOURCE.size();
    }
}
複製代碼

持久層寫好了,咱們還要寫一個Bean工廠才能經過靜態方法獲取一個單例的LoginLoggingDAO

/**
 * Bean工廠,用於獲取日誌記錄的DAO
 * @author zhuzh
 * @date 2019.10.17
 */
public class LoginLogBeanFactory {
    private LoginLogBeanFactory(){}
    private static final Object locker = new Object();
    private static LoginLoggingDAO loginLoggingDAO;

    public static LoginLoggingDAO getInstance(){
        if (loginLoggingDAO == null){
            synchronized (locker){
                loginLoggingDAO = new MockLoginLoggingDAOImpl();
            }
        }
        return loginLoggingDAO;
    }

}
複製代碼

當主數據源鏈接超時,咱們要拋出一種特定的異常來作標記,便於handler識別當前情況

/**
 * 使用特定異常標明當前確實是數據源的問題
 * @author zhuzh
 * @date 2019.10.17
 */
public class DatasourceBusyException extends RuntimeException {
    public DatasourceBusyException(String message) {
        super(message);
    }
}
複製代碼

線程的執行任務

/**
 * 記錄登陸日誌的執行任務
 * @author zhuzh
 * @date 2019.10.17
 */
public class LoginLoggingTask implements Runnable {

    private LoginLogPO po;
    public LoginLoggingTask(LoginLogPO po1){
        po = po1;
    }

    @Override
    public void run() {
        LoginLoggingDAO dao = LoginLogBeanFactory.getInstance();
        dao.log(po);
    }

    @Override
    public String toString() {
        return "LoginLoggingTask{" +
                "po=" + po +
                '}';
    }
}
複製代碼

自定義的線程

/**
 * 一個用來異步記錄登陸日誌的線程
 * @author zhuzh
 * @date 2019.10.17
 */
public class LoginLoggerThread extends Thread {
    /**
     * 執行任務暫存
     */
    private Runnable task;

    /**
     * 此方法將runnable執行任務賦值給類的成員變量task,便於後續若是出現異常時能夠從新執行任務
     * @param target
     */
    public LoginLoggerThread(Runnable target) {
        super(target);
        task = target;
    }

    public Runnable getTask() {
        return task;
    }
}
複製代碼

和UncaughtExceptionHandler

/**
 * 一個自定義的線程任務異常處理器
 * @author zhuzh
 * @date 2019.10.17
 */
public class LoginLoggingUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        //若是確實是數據源的問題,咱們再切換數據源,若是拋出了別的異常,暫不處理
        if (e instanceof DatasourceBusyException){
            LoginLoggingDAO dao = LoginLogBeanFactory.getInstance();
            dao.logFailed();
            LoginLoggerThread thread = (LoginLoggerThread)t;
            Runnable task = thread.getTask();
            System.out.println("第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task="+task.toString());
            AsynLogger.log(task);
        }
    }
}
複製代碼

接下來咱們再寫一個異步日誌處理類,接收日誌記錄到緩衝隊列

/**
 * 一個登錄日誌異步處理類
 * @author zhuzh
 * @date 2019.10.17
 */
public class AsynLogger {
    /**
     * 日誌緩衝隊列,當用戶登陸成功時,經過調用AsynLogger.log將日誌放入緩衝隊列
     */
    private static final ArrayBlockingQueue<Runnable> TASKS = new ArrayBlockingQueue<>(50);

    /**
     * 初始化50個用戶登陸日誌
     */
    static {
        System.out.println("第一步:初始化50條登陸日誌數據放入[日誌緩衝隊列TASKS]");
        for (int i = 0; i < 50; i++) {
            TASKS.offer(new LoginLoggingTask(new LoginLogPO(i,"用戶"+i,"2019-10-1"+i)));
        }
    }
    public static void log(Runnable loggingTask){
        TASKS.offer(loggingTask);
    }

    /**
     * 調用startLogging方法啓動一個線程輪訓緩衝隊列
     */
    public static void startLogging(){
        Thread a = new Thread(()->{
        while (true){
            Runnable task = TASKS.poll();
            if (task==null){
                continue;
            }
            LoginLoggerThread thread = new LoginLoggerThread(task);
            thread.setUncaughtExceptionHandler(new LoginLoggingUncaughtExceptionHandler());
            thread.start();
        }});
        a.setDaemon(true);
        a.start();
        System.out.println("第二步:建立獨立守護線程輪訓[日誌緩衝隊列TASKS],若是有數據,建立一個線程去處理,而且設置UncaughtExceptionHandler");
    }



}
複製代碼

最後咱們再寫一個main方法,get it done!

/**
 * 測試功能主類
 * @author zhuzh
 * @date 2019.10.17
 */
public class UncaughtExceptionHandlerExample {

    public static void main(String[] args){
        AsynLogger.startLogging();
        try {
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("第六步:全部任務執行完畢,備用數據源有"+ LoginLogBeanFactory.getInstance().getBackupDatasourceSize()+"條數據");
    }
}
複製代碼

輸出結果

第一步:初始化50條登陸日誌數據放入[日誌緩衝隊列TASKS]
第二步:建立獨立守護線程輪訓[日誌緩衝隊列TASKS],若是有數據,建立一個線程去處理,而且設置UncaughtExceptionHandler
第三步:將主數據源塞滿,模擬主數據源鏈接池資源不足
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=33, username=用戶33, createTime=2019-10-133)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=38, username=用戶38, createTime=2019-10-138)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=5, username=用戶5, createTime=2019-10-15)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=39, username=用戶39, createTime=2019-10-139)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=34, username=用戶34, createTime=2019-10-134)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=36, username=用戶36, createTime=2019-10-136)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=47, username=用戶47, createTime=2019-10-147)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=37, username=用戶37, createTime=2019-10-137)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=8, username=用戶8, createTime=2019-10-18)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=46, username=用戶46, createTime=2019-10-146)}
主數據源繁忙,已切換數據源爲備數據源,當前記錄失敗次數=10,備用數據源標誌位=true,將使用備用數據源
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=23, username=用戶23, createTime=2019-10-123)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=40, username=用戶40, createTime=2019-10-140)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=13, username=用戶13, createTime=2019-10-113)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=17, username=用戶17, createTime=2019-10-117)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=45, username=用戶45, createTime=2019-10-145)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=15, username=用戶15, createTime=2019-10-115)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=44, username=用戶44, createTime=2019-10-144)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=12, username=用戶12, createTime=2019-10-112)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=31, username=用戶31, createTime=2019-10-131)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=18, username=用戶18, createTime=2019-10-118)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=35, username=用戶35, createTime=2019-10-135)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=43, username=用戶43, createTime=2019-10-143)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=29, username=用戶29, createTime=2019-10-129)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=30, username=用戶30, createTime=2019-10-130)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=19, username=用戶19, createTime=2019-10-119)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=16, username=用戶16, createTime=2019-10-116)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=7, username=用戶7, createTime=2019-10-17)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=27, username=用戶27, createTime=2019-10-127)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=11, username=用戶11, createTime=2019-10-111)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=6, username=用戶6, createTime=2019-10-16)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=4, username=用戶4, createTime=2019-10-14)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=28, username=用戶28, createTime=2019-10-128)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=3, username=用戶3, createTime=2019-10-13)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=20, username=用戶20, createTime=2019-10-120)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=2, username=用戶2, createTime=2019-10-12)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=41, username=用戶41, createTime=2019-10-141)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=9, username=用戶9, createTime=2019-10-19)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=22, username=用戶22, createTime=2019-10-122)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=1, username=用戶1, createTime=2019-10-11)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=0, username=用戶0, createTime=2019-10-10)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=25, username=用戶25, createTime=2019-10-125)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=42, username=用戶42, createTime=2019-10-142)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=48, username=用戶48, createTime=2019-10-148)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=14, username=用戶14, createTime=2019-10-114)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=49, username=用戶49, createTime=2019-10-149)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=26, username=用戶26, createTime=2019-10-126)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=24, username=用戶24, createTime=2019-10-124)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=21, username=用戶21, createTime=2019-10-121)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=10, username=用戶10, createTime=2019-10-110)}
第四步:線程執行異常,進入UncaughtExceptionHandler,任務從新丟回任務隊列,task=LoginLoggingTask{po=LoginLogPO(id=32, username=用戶32, createTime=2019-10-132)}
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第五步:入庫成功,入庫成功的數據源爲,currentDatasource=BACKUP_DATASOURCE
第六步:全部任務執行完畢,備用數據源有50條數據

Process finished with exit code 0
複製代碼

其實這種每次起一個線程去處理任務的方式是不正確的,應該用線程池,可是本文主要目的是講解Thread的內部接口UncaughtExceptionHandler,後面我會用更好的方案來修改這個例子

本文的源代碼在github上,能夠到這裏下載

相關文章
相關標籤/搜索