AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

故障現場 | 這個死鎖出奇的詭異

來源: 責編: 時間:2024-02-01 12:52:21 247觀看
導讀1. 問題&分析線程池用多了總會出現些詭異問題,特別是當任務間的關系比較復雜時,經常會出現讓你想象不到問題,比如這次出現的這個問題。1.1. 案例突然間,系統出現大量報警,具體信息如下:圖片從拋出的異??芍?,提交量較大導致

1. 問題&分析

線程池用多了總會出現些詭異問題,特別是當任務間的關系比較復雜時,經常會出現讓你想象不到問題,比如這次出現的這個問題。UoI28資訊網——每日最新資訊28at.com

1.1. 案例

突然間,系統出現大量報警,具體信息如下:UoI28資訊網——每日最新資訊28at.com

圖片圖片UoI28資訊網——每日最新資訊28at.com

從拋出的異??芍?,提交量較大導致線程池資源被耗盡,從而觸發了線程池的拒絕策略,直接拋出了 RejectedExecutionException。UoI28資訊網——每日最新資訊28at.com

開始的時候,小艾認為等高峰流量過去后,系統便能恢復正常??沙龊跻饬系氖?,系統一直沒有恢復,那么流量已經將至個位數,請求也是 100% 失敗,同時該節點的大量后臺任務都出現異常。沒有辦法,為了快速止損,不得已對異常節點進行重啟,系統隨之恢復正常,日志輸入如下:UoI28資訊網——每日最新資訊28at.com

圖片圖片UoI28資訊網——每日最新資訊28at.com

其他的后臺任務也恢復正常。UoI28資訊網——每日最新資訊28at.com

驚魂初定的小艾找到出問題的代碼如下:UoI28資訊網——每日最新資訊28at.com

@GetMapping("syncSubmit")public RestResult<String> syncSubmit(String taskName){    this.executeService.submit(new ParentTask());    return RestResult.success("提交成功");}class ParentTask implements Callable<Boolean>{    @Override    public Boolean call() throws Exception {        Future<A> aFuture = executeService.submit(new FetchAChildTask());        doSomeThing(500);        Future<B> bFuture = executeService.submit(new FetchBChildTask());        doSomeThing(500);        C c = buildC(aFuture.get(), bFuture.get());        Future<Boolean> cFuture = executeService.submit(new SaveCChildTask(c));        return cFuture.get();    }}

代碼的邏輯非常簡單,核心流程如下圖所示:UoI28資訊網——每日最新資訊28at.com

圖片圖片UoI28資訊網——每日最新資訊28at.com

核心流程為:UoI28資訊網——每日最新資訊28at.com

  1. 提交異步任務,并行獲取 A 和 B
  2. 線程同步處理一下耗時操作
  3. 獲取 A 和 B 結果后,構建新對象 C
  4. 將 C 保存到數據庫

邏輯非常簡單,唯一的復雜點在于:==多處任務提交使用了統一線程池。==UoI28資訊網——每日最新資訊28at.com

【背景】考慮到線程池是系統中最寶貴的資源,公司“大?!狈庋b了一個全局的 GlobalExecuteService 服務,并制定規范要求所有異步任務統一使用 GlobalExecuteService 來完成。如果需要構建自己的線程池,需要向他提交審批,只有在審批后才能創建新的線程池。UoI28資訊網——每日最新資訊28at.com

1.2. 問題分析

線程池處于什么狀態?為什么所有異步任務都無法提交?UoI28資訊網——每日最新資訊28at.com

這是一個比較燒腦的問題,單盤邏輯沒有什么頭緒,沒有辦法只能將現場 dump 下來進行分析。UoI28資訊網——每日最新資訊28at.com

第一個問題:線程池線程都處于什么狀態,線程棧信息如下:UoI28資訊網——每日最新資訊28at.com

圖片圖片UoI28資訊網——每日最新資訊28at.com

從日志中可知:UoI28資訊網——每日最新資訊28at.com

  1. 線程池中的線程全部處于 WAITING,也就是等待狀態;
  2. 展開 Thread-1 棧信息,發現線程再調用 future.get 操作時出現阻塞
  3. 實際等待對象為 FutrueTask#10

接下來,需要進一步確認 FutureTask#10 具體處于什么狀態,從內存堆中找到 FutureTask#10 對象,詳細信息如下:UoI28資訊網——每日最新資訊28at.com

圖片圖片UoI28資訊網——每日最新資訊28at.com

從日志中可以看出:UoI28資訊網——每日最新資訊28at.com

  1. FutureTask#10 是 LinkedBlockingQueue 的 Node 節點持有,也就是 FutureTask#10 處于等待隊列中
  2. 該阻塞隊列屬于 GlobalExecuteService 所有

排查到這里,真相浮出水面:GlobalExecuteService 中的線程,正在等待 GlobalExecuteService 阻塞隊列的任務完成。UoI28資訊網——每日最新資訊28at.com

具體如下圖所示:UoI28資訊網——每日最新資訊28at.com

圖片圖片UoI28資訊網——每日最新資訊28at.com

線程池中的所有工作線程都在等待阻塞隊列的任務完成,由于沒有可用的工作線程,阻塞隊列中的任務永遠都不會被執行。UoI28資訊網——每日最新資訊28at.com

這就是典型的死鎖!!!UoI28資訊網——每日最新資訊28at.com

2. 解決方案

費了老大勁終于定位問題,解決思路也就變的明了:不要向自己運行的線程池提交任務。UoI28資訊網——每日最新資訊28at.com

圖解如下:UoI28資訊網——每日最新資訊28at.com

圖片圖片UoI28資訊網——每日最新資訊28at.com

線程池不會向自己提交任務,而是將任務提交到其他線程池。UoI28資訊網——每日最新資訊28at.com

2.1. 手工拆分線程池

問題修復變的簡單,我們需要:UoI28資訊網——每日最新資訊28at.com

  1. 先建一個子線程池服務
  2. 父線程池向子線程池提交任務

具體代碼如下:UoI28資訊網——每日最新資訊28at.com

@Autowiredprivate GlobalExecuteService executeService;// 創建新的線程池服務@Autowiredprivate SubExecuteService subExecuteService;@GetMapping("syncSubmit")public RestResult<String> syncSubmit(String taskName){    this.executeService.submit(new ParentTask());    return RestResult.success("提交成功");}class ParentTask implements Callable<Boolean>{    @Override    public Boolean call() throws Exception {        log.info("Begin to Run Parent Task");        // 向新的線程池服務提交任務        Future<A> aFuture = subExecuteService.submit(new FetchAChildTask());        doSomeThing(500);        // 向新的線程池服務提交任務        Future<B> bFuture = subExecuteService.submit(new FetchBChildTask());        doSomeThing(500);        C c = buildC(aFuture.get(), bFuture.get());        // 向新的線程池服務提交任務        Future<Boolean> cFuture = subExecuteService.submit(new SaveCChildTask(c));        Boolean result = cFuture.get();        log.info("End to Run Parent Task");        return result;    }}

2.2. 多級任務管理

手工拆分線程池確實能解決這個場景的問題,但由于 GlobalExecuteService 服務已經使用很長時間,任務間的關系錯綜復雜,很難一次性排查并修復所有問題,同時隨著邏輯的變化未來仍舊會出現類似的問題。UoI28資訊網——每日最新資訊28at.com

那最佳方案是什么?UoI28資訊網——每日最新資訊28at.com

讓 GlobalExecuteService 具備多級管理能力。核心代碼如下:UoI28資訊網——每日最新資訊28at.com

@Servicepublic class GlobalExecuteServiceV2 {    // 記錄當前線程運行級別,默認 0,表示當前線程非該類管理的線程池線程    private static final ThreadLocal<Integer> LEVEL_HOLDER = ThreadLocal.withInitial(()->0);    // 一級線程池    private ExecutorService executorServiceLeve1;    // 二級線程池    private ExecutorService executorServiceLeve2;    // 默認線程池    private ExecutorService defExecutorService;    @PostConstruct    public void init() {       // 省略線程池初始化邏輯    }    public <T> Future<T> submit(Callable<T> callable){        // 獲取當前線程的運行級別        Integer level = LEVEL_HOLDER.get();        // 根據當前運行級別,計算子任務所使用的線程池        ExecutorService executorService = getNextExecutorServiceByLevel(level);        // 為子任務分配運行級別        CallableWrapper<T> callableWrapper = new CallableWrapper<>(level + 1, callable);        // 提交任務        return executorService.submit(callableWrapper);    }    private ExecutorService getNextExecutorServiceByLevel(Integer level) {        if (level == 0){            return executorServiceLeve1;        }        if (level == 1){            return executorServiceLeve2;        }        return defExecutorService;    }    class CallableWrapper<T> implements Callable<T>{        private final Integer level;        private final Callable<T> callable;        CallableWrapper(Integer level, Callable<T> callable) {            this.level = level;            this.callable = callable;        }        @Override        public T call() throws Exception {            try {                // 為線程池綁定運行級別                LEVEL_HOLDER.set(level);                return callable.call();            }finally {                // 清理線程池運行級別                LEVEL_HOLDER.remove();            }        }    }}

核心設計如下:UoI28資訊網——每日最新資訊28at.com

  1. 使用 ThreadLocal<Integer> LEVEL_HOLDER 記錄當前線程運行的級別,默認為 0 表示任務未在線程池中運行
  2. 提交任務時,通過當前運行級別計算下一級別的線程池
  • 當前級別為0,返回 Level1 線程池
  • 當前級別為1,返回 Level2 線程池
  • 其他基本,返回 默認 線程池
  1. 通過 CallableWrapper 自動將任務運行的 Level 綁定到當前線程上下文
  2. 任務執行前,使用 LEVEL_HOLDER.set(level) 完成運行 level 的設置
  3. 任務執行后,使用 LEVEL_HOLDER.remove() 完成運行 level 的清理

為了演示方便,僅定義了 3 級線程池,通常情況下足夠業務使用,但需要注意:UoI28資訊網——每日最新資訊28at.com

  • 超過三級提交,仍舊有可以出現死鎖的情況,可以通過日志方式及時暴露問題
  • 如不放心,可以升級為 “無限極” 設計,及使用 List<ExecutorService> 對線程池進行統一管理,并根據 Level 完成線程池的動態創建

3. 示例&源碼

代碼倉庫:UoI28資訊網——每日最新資訊28at.com

https://gitee.com/litao851025/learnFromBugUoI28資訊網——每日最新資訊28at.com

代碼地址:UoI28資訊網——每日最新資訊28at.com

https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/thread/deadlockUoI28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-70483-0.html故障現場 | 這個死鎖出奇的詭異

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: CSS 和 SVG 實現彩色圖片陰影

下一篇: 我們一起聊聊React列表渲染與Key

標簽:
  • 熱門焦點
  • Find N3入網:最高支持16+1TB

    OPPO將于近期登場的Find N3折疊屏目前已經正式入網,型號為PHN110。本次Find N3在外觀方面相比前兩代有很大的變化,不再是小號的橫向折疊屏,而是跟別的廠商一樣采用了較為常見的
  • JavaScript 混淆及反混淆代碼工具

    介紹在我們開始學習反混淆之前,我們首先要了解一下代碼混淆。如果不了解代碼是如何混淆的,我們可能無法成功對代碼進行反混淆,尤其是使用自定義混淆器對其進行混淆時。什么是混
  • 掘力計劃第 20 期:Flutter 混合開發的混亂之治

    在掘力計劃系列活動第20場,《Flutter 開發實戰詳解》作者,掘金優秀作者,Github GSY 系列目負責人戀貓的小郭分享了Flutter 混合開發的混亂之治。Flutter 基于自研的 Skia 引擎
  • SpringBoot中使用Cache提升接口性能詳解

    環境:springboot2.3.12.RELEASE + JSR107 + Ehcache + JPASpring 框架從 3.1 開始,對 Spring 應用程序提供了透明式添加緩存的支持。和事務支持一樣,抽象緩存允許一致地使用各
  • 學習JavaScript的10個理由...

    作者 | Simplilearn編譯 | 王瑞平當你決心學習一門語言的時候,很難選擇到底應該學習哪一門,常用的語言有Python、Java、JavaScript、C/CPP、PHP、Swift、C#、Ruby、Objective-
  • 每天一道面試題-CPU偽共享

    前言:了不起:又到了每天一到面試題的時候了!學弟,最近學習的怎么樣啊 了不起學弟:最近學習的還不錯,每天都在學習,每天都在進步! 了不起:那你最近學習的什么呢? 了不起學弟:最近在學習C
  • ESG的面子與里子

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之三伏大幕拉起,各地高溫預警不絕,但處于厄爾尼諾大&ldquo;烤&rdquo;之下的除了眾生,還有各大企業發布的ESG報告。ESG是&ldquo;環境保
  • 三星獲批量產iPhone 15全系屏幕:蘋果史上最驚艷直屏

    按照慣例,蘋果將繼續在今年9月舉辦一年一度的秋季新品發布會,有傳言稱發布會將于9月12日舉行,屆時全新的iPhone 15系列將正式與大家見面,不出意外的話
  • 榮耀Magic4 至臻版 首創智慧隱私通話 強勁影音系統

    2022年第一季度臨近尾聲,在該季度內,許多品牌陸續發布自己的最新產品,讓大家從全新的角度來了解當今的手機技術。手機是電子設備中,更新迭代十分迅速的一款產品,基
Top