ThreadPoolExecutor是如何處理任務的異常情況

本文因生產環境線程池某些場景下的任務異常後,日誌文件中沒有被記錄進來產生的困惑引發的思考。
當然如果所有異步的業務方法run裏面都加上一層try…catch…就可以主動捕獲所有的異常,也能夠記錄到日誌文件中,然而總有一些人總有一些時候不小心漏掉了,今天分享下run方法如果不加try…catch…的後果

測試調用execute

//測試代碼
public static void testExecute() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });

    }
}

//測試結果  控制檯輸出
2020-06-04 00:00:50.118 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc0
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc1
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc2
2020-06-04 00:00:51.837 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         91 lzc4
Exception in thread "pool-1-thread-1" 
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testExecute$2(ExceptionPoolDemo.java:89)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

//測試結果  日誌文件輸出
2020-06-04 00:00:50.118 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc0
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc1
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc2
2020-06-04 00:00:51.837 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         91 lzc4

測試結論

1.控制檯會有異常堆棧信息 , 然而日誌文件中卻沒有記錄這條異常,這個很致命,異步處理的線程出現了異常,日誌沒有記錄事件,事後很難排查
2.corePoolSize設置的是1 , blockinqueue使用的是無界隊列,正常情況下,始終只會有一個線程來所有任務,即pool-1-thread-1,然而從上面的日誌可以發現有新線程pool-1-thread-2參加工作了

測試調用submit

//測試代碼
public static void testSubmit() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.submit(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });



    }
}

//測試結果  控制檯輸出

2020-06-04 00:15:25.069 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc0
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc1
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc2
2020-06-04 00:15:25.073 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc4

//測試結果  日誌文件輸出
2020-06-04 00:15:25.069 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc0
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc1
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc2
2020-06-04 00:15:25.073 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc4

測試結論

1.這個更要命,控制檯與日誌文件都沒有記錄這條異常,後果同上
2.submit時沒有使用新工作線程,使用始終使用的是pool-1-thread-1

測試調用submit+future.get()

//測試代碼
public static void testSubmitFuture() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    List<Future> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        int finalI = i;
        Future f = pool.submit(() -> {
            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });
        list.add(f);
    }

    for (Future f : list) {
        try {
            f.get();
        } catch (Exception e) {
            logger.error("出現了異常", e);
        }
    }

}

//測試結果  控制檯輸出

2020-06-04 00:18:55.784 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc0
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc1
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc2
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc4
2020-06-04 00:18:55.790 ERROR main com.alioo.pool.ExceptionPoolDemo         144 出現了異常
java.util.concurrent.ExecutionException: java.lang.RuntimeException: abcdefg
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.alioo.pool.ExceptionPoolDemo.testSubmitFuture(ExceptionPoolDemo.java:142)
	at com.alioo.pool.ExceptionPoolDemo.main(ExceptionPoolDemo.java:153)
Caused by: java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testSubmitFuture$4(ExceptionPoolDemo.java:132)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

//測試結果  日誌文件輸出
2020-06-04 00:18:55.784 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc0
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc1
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc2
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc4
2020-06-04 00:18:55.790 ERROR main com.alioo.pool.ExceptionPoolDemo         144 出現了異常
java.util.concurrent.ExecutionException: java.lang.RuntimeException: abcdefg
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.alioo.pool.ExceptionPoolDemo.testSubmitFuture(ExceptionPoolDemo.java:142)
	at com.alioo.pool.ExceptionPoolDemo.main(ExceptionPoolDemo.java:153)
Caused by: java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testSubmitFuture$4(ExceptionPoolDemo.java:132)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

測試結論

1.future.get()表示接收返回值,當調用future.get()後,控制檯與日誌文件均記錄下來了,完美!
2.submit時沒有使用新工作線程,使用始終使用的是pool-1-thread-1

原因分析

原因分析 execute

線程池的工作線程類Worker實現了Runnable接口,接下來主要分析Worker的構造方法與run方法

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

public void run() {
    runWorker(this);
}
        
        

解碼:

  1. Worker的構造方法中調用getThreadFactory().newThread(this);通過工廠方法來創建工作線程
  2. 上面的測試代碼創建pool時使用的是MyThreadFactory,代碼在文章最後,上面測試的案例使用的MyThreadFactory跟Executors.defaultThreadFactory()一模一樣
  3. 創建線程前被要求先創建一個group,閱讀代碼會發現group其實是Thread.currentThread().getThreadGroup(),ThreadGroup實現了Thread.UncaughtExceptionHandler接口
Thread t = new Thread(group, r,
        namePrefix + threadNumber.getAndIncrement(),
       0);
  1. 分析下ThreadGroup.uncaughtException方法,當線程出現未捕獲的異常時就會進入到這裏,看代碼有點類似於雙親委派機制,優先交給parent來處理異常。
    實際斷點發現會進入2次這個方法,第1次由於parent不爲空(parent:java.lang.ThreadGroup[name=system,maxpri=10]),第2次爲空
    第2次進入後,ueh爲null,所以執行了else if代碼塊,結果跟上面測試execute會發現是可以對上的

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);
        }
    }
}
  1. 前面幾步分析是有個前提的,就是當Worker線程出現了未捕獲的異常才能走上面的uncaughtException方法,雖然我們之前交待了業務方法出現了異常未捕獲,但是還要進一步看看Worker.run()方法是否會幫我們try…catch…
    (這裏也很關鍵,也是這裏的代碼差異造成了上面3種不同的結果)
    Worker.run()方法直接調用了runWorker()方法

    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;         //第1層try...塊,業務邏輯會拋出RuntimeException,所以會進入到這裏,這裏雖然捕獲了,然而又throw了
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {                       //第2層try...塊,這裏的try...塊沒有catch,是不是很驚喜,收到第1個層throw出現的RuntimeException只需要做finally邏輯,然後RuntimeException繼續往外拋
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {                             //第3層try...塊,這裏的try...塊依舊沒有catch,收到第2層throw出現的RuntimeException依舊只需要做finally邏輯,繼續往外拋RuntimeException
                                                //然而已經到最外層了,還沒有被catch住的話就會交給Thread.currentThread().getThreadGroup().uncaughtException進行處理了。
            processWorkerExit(w, completedAbruptly);
        }
    }

原因分析 submit

前面已經鋪墊了Worker.run()方法代碼差異造成了submit後異常處理的不同,接下來具體分析下吧

  1. submit方法調用時對原始的業務方法對象task進行了包裝,生成了新的Rannable對象FutureTask

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
    
  1. 下面是Rannable對象FutureTask對象的run方法,會在這個裏面調用原始的業務方法```c.call()``

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();             //原始的業務方法call()會在這裏進行調用
                ran = true;
            } catch (Throwable ex) {
                result = null;                //第1層try...塊,這裏的try...塊遇到RuntimeException直接喫掉了,並沒有繼續往外拋
                ran = false;
                setException(ex);             //進入這個方法會將ex賦值到outcome對象中(注意正常情況下outcome存放的是方法返回值result)
            }
            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)                    //第2層try...塊,當出現異常時,這裏的if不會進去執行
            handlePossibleCancellationInterrupt(s);
    }                                            
}


protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}
    
  1. 當整個run方法執行完成後,異常存放到了outcome屬性中,並沒有任何地方打印輸出,所以跟前面測試submit情況也是符合的

原因分析 submit+future.get()

前面的測試結論中可以看到只調用submit時異常信息是沒有任何輸出的,而當調用了future.get()異常就出來了,這是爲什麼呢,下面對着源碼來揭曉答案

  1. 由前面的代碼我們知道Future實現是FutureTask,查看下FutureTask.get()。異常發生時state= (EXCEPTIONAL = 3),這個方法中的if不會進去,直接會調用report(s)
    report(s)中的2個if也不滿足條件,也不會執行,最終就會執行throw new ExecutionException((Throwable)x);,好了,這就是隻調用submit不會輸出異常,而當進一步調用future.get()就會觸發往外拋異常了

private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

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

  1. 由於future.get()方法強制要求調用方捕獲異常,所以我們在調用的地方使用了try…catch…,前面測試時看到的異常日誌都來源自這裏的logger.error("出現了異常", e);
for (Future f : list) {
    try {
        f.get();
    } catch (Exception e) {
        logger.error("出現了異常", e);
    }
}
    
  1. 細心的你或許會發現這個地方的異常堆棧比較奇怪,先打印了ExecutionException,後來又打印了RuntimeException,這裏因爲report方法中對原始異常RuntimeException又包了一層ExecutionException
    本質上還是隻有1個異常的,這樣做是有好處的既告訴了你你捕獲的異常是從哪裏發起的ExecutionException,又告訴了你原始異常發生的地方
2020-06-04 00:18:55.790 ERROR main com.alioo.pool.ExceptionPoolDemo         144 出現了異常
java.util.concurrent.ExecutionException: java.lang.RuntimeException: abcdefg
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.alioo.pool.ExceptionPoolDemo.testSubmitFuture(ExceptionPoolDemo.java:142)
	at com.alioo.pool.ExceptionPoolDemo.main(ExceptionPoolDemo.java:153)
Caused by: java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testSubmitFuture$4(ExceptionPoolDemo.java:132)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)


附文中使用了自定義的ThreadFactory,實現與Executors.defaultThreadFactory()一模一樣

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

public class MyThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    public  MyThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
            Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
            namePrefix + threadNumber.getAndIncrement(),
            0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

兜底解決方案

上面分別分析了execute,submit在業務方法未捕獲異常時的表現,當異常發生時不能記錄到日誌文件中後續定位問題是非常麻煩的,那麼作爲線程框架可以有哪些解決手段呢?

業務方法自行try…catch…,首選方案吧,強烈建議這樣子做

runWorker()方法中會調用afterExecute(task, thrown),可以在重寫這個方法對異常進行日誌記錄

這裏需要說明的是重寫了afterExecute(task, thrown)只是增加了對異常的日誌打印,但是異常還是會繼續往外拋出,所以控制檯會打印2次異常日誌,但是日誌文件只會打印1次(未重寫afterExecute時,控制檯打印1次,日誌文件0次)

//測試代碼

public static void afterExecute() {
    ThreadPoolExecutor executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>()) {
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            if (t != null) {
                logger.error("出現了異常", t);
            }
        }
    };

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        executorService.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("aabb");
            }
            logger.info("lzc" + finalI);

        });

    }

}

//測試結果  控制檯輸出
2020-06-04 12:51:42.070 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc0
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc1
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc2
2020-06-04 12:51:55.549 ERROR pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         23 出現了異常
java.lang.RuntimeException: aabb
	at com.alioo.pool.ExceptionPoolDemo.lambda$afterExecute$0(ExceptionPoolDemo.java:33)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
2020-06-04 12:51:55.552 INFO  pool-2-thread-2 com.alioo.pool.ExceptionPoolDemo         35 lzc4
Exception in thread "pool-2-thread-1" java.lang.RuntimeException: aabb
	at com.alioo.pool.ExceptionPoolDemo.lambda$afterExecute$0(ExceptionPoolDemo.java:33)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)


//測試結果  日誌文件輸出
2020-06-04 12:51:42.070 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc0
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc1
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc2
2020-06-04 12:51:55.549 ERROR pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         23 出現了異常
java.lang.RuntimeException: aabb
	at com.alioo.pool.ExceptionPoolDemo.lambda$afterExecute$0(ExceptionPoolDemo.java:33)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
2020-06-04 12:51:55.552 INFO  pool-2-thread-2 com.alioo.pool.ExceptionPoolDemo         35 lzc4

自定義ThreadGroup

這裏使用了自定義的MyThreadFactory,在MyThreadFactory中創建線程時使用自定義的MyThreadGroup,並且重寫了uncaughtException方法,當遇到異常時記錄到日誌文件裏

//測試代碼
public static void myThreadGroup() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });

    }
}



public class MyThreadFactory implements ThreadFactory {

    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    public MyThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = new MyThreadGroup("mythreadgroup");
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
            namePrefix + threadNumber.getAndIncrement(),
            0);
        if (t.isDaemon()) { t.setDaemon(false); }
        if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); }
        return t;
    }
}

class MyThreadGroup extends ThreadGroup {
    private static Logger logger = LoggerFactory.getLogger(MyThreadGroup.class);

    public MyThreadGroup(String name) {
        super(name);
    }

    @Override
    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)) {
            logger.error("兄弟,你的業務方法出現異常咋不處理呢,Exception in thread \""
                + t.getName() + "\" ",e);

        }
        //}
    }
}

//測試結果  控制檯輸出

2020-06-04 13:59:37.517 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc0
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc1
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc2
2020-06-04 13:59:37.618 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         55 lzc4
2020-06-04 13:59:37.622 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             52 兄弟,你的業務方法出現異常咋不處理呢,Exception in thread "pool-1-thread-1" 
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$1(ExceptionPoolDemo.java:53)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)


//測試結果  日誌文件輸出
2020-06-04 13:59:37.517 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc0
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc1
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc2
2020-06-04 13:59:37.618 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         55 lzc4
2020-06-04 13:59:37.622 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             52 兄弟,你的業務方法出現異常咋不處理呢,Exception in thread "pool-1-thread-1"
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$1(ExceptionPoolDemo.java:53)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
	
	

設置UncaughtExceptionHandler

跟上面的方法類似,都是重寫ThreadFactory,上面是在創建線程時指定ThreadGroup,現在是線程按照默認方式創建完了,重置下ThreadGroup,僅此而已。

//測試代碼
public static void uncaughtExceptionHandler() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory2(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });

    }
}

public class MyThreadFactory2 implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    MyThreadFactory2() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
            Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
            namePrefix + threadNumber.getAndIncrement(),
            0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);

        t.setUncaughtExceptionHandler(new MyThreadGroup2("mythreadgroup"));
        return t;
    }
}

class MyThreadGroup2 extends ThreadGroup {
    private static Logger logger = LoggerFactory.getLogger(MyThreadGroup.class);

    public MyThreadGroup2(String name) {
        super(name);
    }

    @Override
    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)) {
            logger.error("兄弟,你的業務方法出現異常咋不處理呢,Exception in thread \""
                + t.getName() + "\" ",e);

        }
        //}
    }
}

//測試結果  控制檯輸出
2020-06-04 14:17:57.439 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc0
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc1
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc2
2020-06-04 14:17:58.047 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         74 lzc4
2020-06-04 14:17:58.049 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             55 兄弟,你的業務方法出現異常咋不處理呢,Exception in thread "pool-1-thread-1" 
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$2(ExceptionPoolDemo.java:72)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

	
//測試結果  日誌文件輸出
2020-06-04 14:17:57.439 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc0
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc1
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc2
2020-06-04 14:17:58.047 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         74 lzc4
2020-06-04 14:17:58.049 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             55 兄弟,你的業務方法出現異常咋不處理呢,Exception in thread "pool-1-thread-1"
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$2(ExceptionPoolDemo.java:72)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
	

參考文章
https://www.cnblogs.com/Laymen/p/11465881.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章