成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

線程池異常黑洞及其防范策略

開發 前端
當使用 submit 提交任務時,會返回 Futrue 對象,通過 Future 的 get 方法便可以獲取任務運行的異常信息,但這樣會阻塞主線程導致接口響應時間過長。

1. 問題&分析

迭代剛上線,小艾同學又接到了一線業務的投訴,一起看下本次遇到的又是什么問題。

1.1. 案例

上周接到一個需求,需要對系統中的核心操作增加操作日志,也就是在操作完成后對操作人、操作時間等信息進行詳細記錄。核心包括:創建訂單、取消訂單、刪除訂單、修改價格等。

在需求分析時,小艾做了深度思考:記錄操作日志不能影響正常的業務操作,比如創建訂單,哪怕是操作日志記錄失敗,也不能導致下單失敗。

當然,方案1便是,記錄日志的邏輯使用 try-catch 進行處理,哪怕是拋出異常也不能影響原來的主流程。

圖片圖片

這樣做確實能控制住異常,但由于是在主線程中運行,這樣會導致整個流程的處理時間加長。這時,小艾想起了線程池的異常操作,整體如下:

圖片圖片

這個方案好處多多:

  1. 可以非常好對異常進行隔離,任務執行發生異常也不會影響主流程
  2. 操作日志在線程池中異步處理,不會占用主線程的時間,整體操作時間變化不大

核心代碼如下:

@GetMapping("createOrder")
public RestResult<String> createOrder(Integer taskId){
    log.info("begin to create Order {}", taskId);
    // 創建訂單
    doCreateOrder(taskId);
    log.info("end to create Order {}", taskId);

    // 異步保存操作日志
    log.info("Begin to Submit Task {}", taskId);
    this.executorService.execute(new SaveOperationLogTask(taskId));
    log.info("Success to Submit Task {}", taskId);
    return RestResult.success("提交成功");
}

// 保存操作日志 Task
@Slf4j
public class SaveOperationLogTask implements Runnable {
    // 省略部分代碼
    @Override
    public void run() {
        log.info("Begin to save operation");
        // 保存日志
        saveLog();
        log.info("Success to Run task {}", this.taskId);
    }

    private void saveLog() {
        // 實際執行業務邏輯,保存到數據庫
    }
}

在收到業務反饋時,小艾第一時間查看日志,居然沒有找到任何異常信息。難道是業務反饋信息有問題?根據業務提供的訂單號,在數據庫中確實沒有找到操作記錄,好奇怪呀。

從日志中提取出 正常 和 異常 信息,分別如下:

正常日志:

圖片圖片

可以看出:

  1. 在主線程中完成創建訂單和提交任務操作
  2. 在線程池線程中完成操作日志保存操作
  3. 在數據庫中能看到詳細的操作信息

異常日志:

圖片圖片

可以看出少了些信息:

  1. 主線程操作沒有變化,完成了創建訂單和提交任務操作
  2. 線程池線程只打印了開始執行的日志,未見到執行成功日志
  3. 數據中也沒有操作信息

對比日志可見,==保存操作日志的任務執行失敗,同時系統沒有拋出任何異常?。?!==

1.2. 問題分析

核心還是對 線程池的核心 API 不熟悉,當使用 `execute()` 方法提交任務時,異常信息不會直接拋出給調用者。這是因為線程池處理任務的方式是,將這些任務封裝到一個 `Runnable` 中去執行。`Runnable.run()` 方法沒有任何拋出異常的聲明,所以在運行 `Runnable` 時產生的異常只會被內部捕獲,不會拋出。

線程池中提供兩者函數:

  1. `execute()` 方法用于提交不需要返回值的任務,所以無法判斷任務是否被線程池成功執行。
  2. `submit()` 方法用于提交需要返回值的任務。線程池會返回一個 future 類型的對象,通過這個 future 對象可以判斷任務是否執行成功。

2. 解決方案

最大困擾原因是:出問題后沒有任何信息。所以對應的解決方案便是:讓系統能夠打印異常棧暴露異常原因。

2.1. 手工拋出異常

最簡單方式便是,在Task代碼中通 try-catch 手工捕獲并打印異常日志。

詳細代碼如下:

@Slf4j
public class SaveOperationLogTask1 implements Runnable {
    // 省略非核心代碼
    @Override
    public void run() {
        try {
            int result = RandomUtils.nextInt() / this.taskId;
            log.info("Success to Run task {}", this.taskId);
        }catch (Exception e) {
            log.error("failed to run task {}", taskId, e);
        }
    }
}

當出現異常數據時,日志如下:

圖片圖片

可以看出,從 SaveOperationLogTask1 類中清楚的打印異常信息。

2.2. 封裝 Runnable 統一管理異常

每個 Task 都手工添加 try-catch 邏輯,不僅工作量大也非常容易出現遺漏場景,我們需要一個更好的方案。

可以構建一個 Runnable 的封裝類來對異常進行統一處理,詳細代碼如下:

@Slf4j
public class LogBasedTaskWrapper implements Runnable {
    private final Runnable runnable;

    public LogBasedTaskWrapper(Runnable runnable) {
        this.runnable = runnable;
    }

    @Override
    public void run() {
        try {
            this.runnable.run();
        }catch (Exception e) {
            log.error("Filed to run task {}", runnable, e);
        }
    }
}

// 在提交任務時,使用 LogBasedTaskWrapper 對 Task 進行封裝即可
log.info("Begin to Submit Task {}", taskId);
Runnable task = new SaveOperationLogTask(taskId);
this.executorService.execute(new LogBasedTaskWrapper(task));
log.info("Success to Submit Task {}", taskId);

當出現異常數據時,日志如下:

圖片圖片

可以看出,從 LogBasedTaskWrapper 類中清楚的打印異常信息。

2.3. 定制化線程工廠

Wrapper 機制不錯,但需要對 Task 進行封裝操作,還是容易被遺漏,我們還需要更簡單的方式。

可以對線程池的線程工廠進行定制,對為捕獲異常進行特殊處理,詳細代碼如下:

executorServiceV2 = new ThreadPoolExecutor(4, 4,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(20),
        new BasicThreadFactory.Builder()
                .namingPattern("BlackHole_thread-%d")
                // 設置為捕獲異常處理器
                .uncaughtExceptionHandler((t, e) -> log.error("Failed to run task", e))
                .build(),
        new ThreadPoolExecutor.AbortPolicy());

// 然后使用 executorServiceV2 即可
// 異步保存操作日志
log.info("Begin to Submit Task {}", taskId);
this.executorServiceV2.execute(new SaveOperationLogTask(taskId));
log.info("Success to Submit Task {}", taskId);

核心代碼就一句:.uncaughtExceptionHandler((t, e) -> log.error("Failed to run task", e))。當出現未捕獲異常時,會統一被 UncaughtExceptionHandler 處理。詳細日志如下:

圖片圖片

從日志中看到,從 ExceptionBlackHoleFixController 類中對異常進行處理。

這是一勞永逸的方法,也是最鼓勵的方法。

2.4. 使用 CompletableFuture

當使用 submit 提交任務時,會返回 Futrue 對象,通過 Future 的 get 方法便可以獲取任務運行的異常信息,但這樣會阻塞主線程導致接口響應時間過長。

這種情況下,可以使用更高級的 CompletableFuture,向 CompletableFuture 設置異常處理器后,出現異常時會自動調用處理器,核心代碼如下:

// 異步保存操作日志
log.info("Begin to Submit Task {}", taskId);
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(new SaveOperationLogTask(taskId), this.executorService);
completableFuture.exceptionally(e -> {
        log.error("Failed to Submit Task", e);
        return null;
    }
);
log.info("Success to Submit Task {}", taskId);

當出現異常數據時,日志如下:

圖片圖片

image

從日志中看到,從 ExceptionBlackHoleFixController 類中對異常進行處理。

3. 示例&源碼

代碼倉庫:https://gitee.com/litao851025/learnFromBug

代碼地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/thread/exceptionblackhole


責任編輯:武曉燕 來源: geekhalo
相關推薦

2020-11-25 11:33:47

Java線程技術

2025-02-24 00:00:10

.NET線程池模型

2025-05-09 10:14:19

2025-04-28 00:55:00

2020-07-08 12:05:55

Java線程池策略

2024-10-09 15:58:02

2012-02-13 17:26:35

2013-05-09 09:13:41

2022-11-24 09:00:00

2020-02-18 14:25:51

Java線程池拒絕策略

2023-12-29 09:38:00

Java線程池

2024-09-13 09:06:22

2011-09-05 10:40:45

2016-09-01 21:19:55

2014-06-06 09:30:07

2023-05-19 08:01:24

Key消費場景

2010-01-30 11:23:59

2022-03-14 07:32:06

線程池拒絕策略自定義

2011-08-23 08:37:56

2011-03-14 10:10:01

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品一区二区三区在线 | 国产精品揄拍一区二区久久国内亚洲精 | 中文字幕精品一区 | 中文字幕 在线观看 | 国产欧美一区二区三区在线看 | 成人欧美一区二区三区在线播放 | 亚洲精品成人网 | 欧美操操操 | 国产精品一区二区久久精品爱微奶 | 欧美黄视频 | 国产福利小视频 | a级黄色片视频 | 天天看天天干 | 日韩一区二区在线视频 | 日韩 欧美 综合 | 天天色综 | 免费观看国产视频在线 | 国产清纯白嫩初高生视频在线观看 | 日韩电影中文字幕 | 亚洲国产精品久久久久秋霞不卡 | www.国产| 欧美日韩视频 | 亚洲欧美一区二区三区1000 | 欧美一级二级在线观看 | 夜夜爽99久久国产综合精品女不卡 | 91免费小视频 | 欧美一级黑人aaaaaaa做受 | 91人人在线 | 亚洲国产精品一区二区第一页 | 日韩精品av | 日韩欧美精品 | 91精品久久久久 | 女同av亚洲女人天堂 | 欧美日韩在线播放 | 一区二区中文字幕 | 精品一区二区三区四区外站 | 欧洲亚洲视频 | 在线观看国产 | 欧美一级三级在线观看 | 欧美日韩大陆 | 一级欧美黄色片 |