JAVA異常和日誌
未捕獲異常
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 &
問題: 錯誤日誌和正常日誌對不上,不方便排查問題
綜上,可以考慮強制對異常進行處理,一勞永逸。