JAVA異常和日誌

一箇中文名發表於2020-12-11

未捕獲異常

Runnable 未捕獲異常

@Slf4j
public class RunnableDemo implements Runnable{

    boolean flag;

    public RunnableDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        log.info("進入runnableDemo");
        if(flag){
            throw new NumberFormatException("RunnableDemo測試異常");
        }
        log.info("進入runnableDemo 結束");
    }
 }

以下日誌列印在控制,不列印在日誌檔案中

10:09:17.110 [Thread-0] INFO  c.z.d.exception.utils.RunnableDemo - 進入runnableDemo
Exception in thread "Thread-0" java.lang.NumberFormatException: RunnableDemo測試異常
	at com.zhou.demo.exception.utils.RunnableDemo.run(RunnableDemo.java:29)
	at java.lang.Thread.run(Thread.java:745)

 因為其實執行的是 java.lang.ThreadGroup#uncaughtException;輸出日誌是System.err.print,所以不會輸出到日誌檔案。

執行緒池未捕獲異常

執行緒池會捕獲任務丟擲的異常和錯誤,處理策略會受到我們提交任務的方式而不同。

pool.execute

public static void testThreadPoolExec(Boolean flag){
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    Thread thread = new Thread(new RunnableDemo(flag));
    executorService.execute(thread);
}

只有通過execute提交的任務,才能將它丟擲的異常交給UncaughtExceptionHandler

實際也是執行 java.lang.ThreadGroup#uncaughtException

效果類似,列印在控制檯不列印在日誌檔案

pool.submit

public static void testThreadPoolSumbit(Boolean flag){
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    Thread thread = new Thread(new RunnableDemo(flag));

    //TODO  沒有以下,錯誤日誌不會列印到控制檯     e.printStackTrace()不會列印到logback日誌  列印到日誌使用log.error
    Future<?> submit = executorService.submit(thread);
    try {
        submit.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

而通過submit提交的任務,submit()方式提交的任務會返給我們一個Future,無論是丟擲的未檢測異常還是已檢查異常,都將被認為是任務返回狀態的一部分。如果一個由submit提交的任務由於丟擲了異常而結束,那麼這個異常將被Future.get封裝在ExecutionException中重新丟擲。

UncaughtExceptionHandler

實現Thread的UncaughtExceptionHandler介面

@Slf4j
public class MyExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        log.error("Exception in thread {}",  t.getName(), e);
    }
}

 

###執行緒
Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler)
//設定全域性的預設異常處理機制
Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler) 

除了執行緒池的sumbit方法,其他都按照MyExceptionHandler的log error列印到日誌中了。

@Slf4j
public class RunnableHnadlerDemo implements Runnable{
   
     boolean flag;

    public RunnableHnadlerDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        log.info("進入runnableDemo");
        if(flag){
            throw new NumberFormatException("RunnableDemo測試異常");
        }
        log.info("進入runnableDemo 結束");
    }

    public static void testRunnable(Boolean flag){
        Thread thread = new Thread(new RunnableDemo(flag));
        thread.start();
    }

    public static void testThreadPoolExec(Boolean flag){
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Thread thread = new Thread(new RunnableDemo(flag));
        executorService.execute(thread);
    }

    public static void lambadThread(){
        Thread thread = new Thread(() -> {
            log.info("進入 lambadThread");
            if (true) {
                throw new NumberFormatException("lambadThread測試異常");
            }
            log.info("進入lambadThread 結束");
        });
        thread.start();
    }

    public static void testThreadPoolSumbit(Boolean flag){
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Thread thread = new Thread(new RunnableDemo(flag));
        Future<?> submit = executorService.submit(thread);
    }

    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new MyExceptionHandler());
        //testRunnable(true);
        //testThreadPoolExec(true);
        //lambadThread();
        testThreadPoolSumbit(true);

    }

### 執行緒池

執行緒池的UncaughtExceptionHandler我們需要一個個設定,藉助工具Apache Commons 和 Google Guava,可以實現,參考。

結論:

執行緒中未捕獲異常不會出現的日誌檔案!!!

執行緒池中sumbit提交的任務,異常封裝在ExecutionException,不判斷結果類方法不會輸出異常!!!

所以一定要注意對執行緒中異常的處理,不然任務可能失敗的毫無聲息。

使用CompletableFuture時候,使用handle或者exceptionally;以便以處理有可能的異常

## handle 異常返回handle的值,正常會返回正常值
### 當執行時出現了異常,可以通過exceptionally進行補償
public static void testFutureR(Boolean flag){
    CompletableFuture.runAsync(() -> {
        log.info("進入CompletableFuture");
        if (flag) {
            throw new RuntimeException("測試非同步異常");
        }
        log.info("進入CompletableFuture結束");
    }).whenComplete((r, e) ->{
        if(e != null){
            log.error("執行未完成出現異常", e);
        }
    }).exceptionally(e -> {
        log.error("exceptionally出現異常", e);
        return null;
    }).handle((t, e) -> {
        log.error("hander 處理異常", e);
        return null;
    }).join();
}

 日誌記錄和nohup

Linux的3種重定向 0:表示標準輸入 1:標準輸出,在一般使用時,預設的是標準輸出 2:標準錯誤資訊輸出

可以使用nohup執行命令,將日誌輸出到檔案,但是這樣缺乏日誌框架對日誌的細緻處理,控制單個日誌的大小和控制總體的日誌數量;

nohup產生的日誌檔案常常會太大佔滿磁碟或者不方便開啟

nohup java -jar  XX.jar> console.out 2>&1 &

 方法1 安裝cronolog切割日誌

方法2 命令列切割日誌,然後清空日誌

cat /dev/null > nohup.out

方法3 不重定向,輸出logBack日誌檔案,問題 未捕獲異常

## 什麼日誌也不要
nohup java -jar  XX.jar> /dev/null 2>&1 &

方法4 只輸出錯誤日誌到nohup日誌檔案

nohup java -jar  XX.jar >/dev/null 2>log &

問題: 錯誤日誌和正常日誌對不上,不方便排查問題

綜上,可以考慮強制對異常進行處理,一勞永逸 。

相關文章