一個countDown在多線程調度下使用不當的分享

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

一個countDown在多線程調度下使用不當的分享

1. 詭異的數據抖動

在一個需求開發過程中,由于有多角色需要獲取每個角色下的菜單;結果出現了單角色下拉去菜單沒問題,多角色情況下只有一個角色的菜單正常返回的問題。這個問題很憂傷,沒有菜單如何進功能頁面?

2. 懷疑是緩存

因為多角色下菜單采用了Redis緩存,故而懷疑是其中一個角色下的菜單是緩存失效,但是關閉掉緩存依然不起作用,排除緩存影響。

3. debug發現問題

通過增加日志輸出,在關閉掉緩存的實時模式下,依然存在菜單時而有,時而沒有的情況。證明應該是代碼有問題。

在一個多線程調度調度服務類中,發現一個問題,即在debug到如下代碼,會出現后續代碼未執行完全,接口結果即被返回的情況。

//經過查詢相關API,不會出現時序問題,可放心使用final CountDownLatch latch = new CountDownLatch(callableList.size());for(Callable callable :callableList){ListenableFuture<T> listenableFuture = threadPoolTaskExecutor.submitListenable(callable);listenableFuture.addCallback(new ListenableFutureCallback<T>() {@Overridepublic void onFailure(Throwable throwable) {//過早調用countDown BUGlatch.countDown();LogHelper.EXCEPTION.error("執行任務異常",throwable);if(futureCallback!=null){futureCallback.onFailure(throwable);}}@Overridepublic void onSuccess(T t) {//過早調用countDown BUGlatch.countDown();if(futureCallback!=null){futureCallback.onSuccess(t);}}});}

4. countDown調用時機不對

在調用遠程接口返回后,立即執行 lacth.countDown(); 會立刻造成主線程阻塞釋放,立即響應結果,丟失部分數據。如下圖所示,在第二個任務獲取數據處理完成后, 就立即調用latch.countDown(), 致使后續的回調還未執行。主線程在收到 latch的釋放阻塞后,返回了不完整的數據結果。

錯誤的調用時序

改造后,將countDown放在回調執行完成之后,并放置在 try {} finally { }代碼塊之中,保證一定得到執行,防止拋異常后,主線程阻塞。 如下所示:

        final CountDownLatch latch = new CountDownLatch(callableList.size());for(Callable callable :callableList){ListenableFuture<T> listenableFuture = threadPoolTaskExecutor.submitListenable(callable);listenableFuture.addCallback(new ListenableFutureCallback<T>() {@Overridepublic void onFailure(Throwable throwable) {try{LogHelper.DEFAULT.info("多線程回調,latchCount="+latch.getCount());LogHelper.EXCEPTION.error("執行任務異常",throwable);if(futureCallback!=null){futureCallback.onFailure(throwable);}}finally {latch.countDown();}}@Overridepublic void onSuccess(T t) {try{LogHelper.DEFAULT.info("多線程回調成功,latchCount="+latch.getCount());if(futureCallback!=null){futureCallback.onSuccess(t);}}finally {latch.countDown();}}});}

5. 本次使用的多線程調度說明

使用Future阻塞模式,不會出現以上問題,使用future.get()的阻塞式獲取,不需要CountDownLatch工具類配合使用。而且本次其實也可以使用Future來實現同樣的功能。但是Future沒有提供 onFailure, onSucess 這樣的回調接口,考慮到易用性,采用了ListenableFuture;

本次采用的 spring的 ListenableFuture 方式回調來實現回調式聚合。在對CountDownLatch使用不當的情況下,出現了該問題。解決該問題后,功能運行正常。

本次提供的服務類 ThreadPoolExecutorService, 其主要的方法如下:


/*** 線程池服務類** @author David* @since 2018/5/15*/
public interface ThreadPoolExecutorService {/*** 執行多線程任務處理,并通過 futureCallback對結果進行回調處理* @param callableList  異步線程可執行的任務 List* @param futureCallback  異步回調* @param timeout 超時時間,毫秒* @param <T>*/<T> void execute(List<Callable<T>> callableList, ListenableFutureCallback<T> futureCallback, long timeout);/*** 執行多線程任務處理,并將結果聚合成一個List 進行返回** @param callableList   異步線程可執行的任務 List* @param failureCallback  失敗的回調方法* @param skipNull        是否忽略Null 結果,如果忽略 null 不會添加到List 之中* @param timeout 超時時間,毫秒* @param <T>*/<T> List<T> executeAndMerge(List<Callable<T>> callableList, FailureCallback failureCallback, boolean skipNull, long timeout);/*** 執行多線程任務處理,并將結果List,聚合成一個List 進行返回** @param callableList 異步線程可執行的任務 List* @param failureCallback 失敗的回調方法* @param timeout 超時時間,毫秒* @param <T>*/<T> List<T> executeAndMergeList(List<Callable<List<T>>> callableList, FailureCallback failureCallback, long timeout);

6. Future 和 ListenableFuture的區別

spring 或者 guava 的 ListenableFutrue其使用方式和機理應該是類似的,這里的說明是通用的。本次工具類使用的是spring自帶的ListenableFutrue。

6.1 區別說明

ListenableFuture顧名思義就是可以監聽的Future,它是對java原生Future的擴展增強。我們知道Future表示一個異步計算任務,當任務完成時可以得到計算結果。如果我們希望一旦計算完成就拿到結果展示給用戶或者做另外的計算,就必須使用另一個線程不斷的查詢計算狀態。這樣做,代碼復雜,而且效率低下。使用ListenableFuture幫我們檢測Future是否完成了,如果完成就自動調用回調函數,這樣可以讓主線程不必阻塞,減少并發程序的復雜度。

6.4 spring ListenableFuture使用示例

一個簡單的示例,如果要獲得 ListenableFuture,則需要一個對Java線程池進行修飾過的線程池執行器。如下所示:

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><property name="corePoolSize" value="${threadpool.corePoolSize}" /><property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" /><property name="maxPoolSize" value="${threadpool.maxPoolSize}" /><property name="queueCapacity" value="${threadpool.queueCapacity}" /><property name="rejectedExecutionHandler"><bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /></property></bean>

guava的請參考以下方式進行修飾,這里不進行詳細說明:

   //Java線程池的類型是可選的【根據場景自行構建】   ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

得到修飾過的線程池執行器后,即可提交可Callable任務,得到 ListenableFuture 進行處理。

以下為計算1~5的3次方的結果并輸出,不需要聚合結果,每個線程計算完畢后,立刻輸出結果:

for(int i=1; i<=5; i++){final int num = i;ListenableFuture<Integer> listenableFuture = threadPoolTaskExecutor.submitListenable(new Callable<Integer>() {@Overridepublic Integer call(){return (int)Math.pow(num,3);}});listenableFuture.addCallback(new ListenableFutureCallback<Integer>() {@Overridepublic void onFailure(Throwable throwable) {LogHelper.EXCEPTION.error("處理失敗", throwable);}@Overridepublic void onSuccess(Integer result) {System.out.println(num + "的3次方為:"+ result);}});}

6.2 是否可以添加多個callback

答案是肯定的,因為ListenableFuture 的addCallback是添加到ListenableFutureCallbackRegistry 一個注冊中心。而注冊中心底層是支持多個回調的。在6.3會具體介紹回調方法注冊中心的處理邏輯。

 public void addCallback(ListenableFutureCallback<? super T> callback) {this.callbacks.addCallback(callback);}

6.3 addCallback是否需要考慮時序

guava 和 spring的 ListenableFuture 均做了時序兼容,在listenableFuture執行的任意時刻調用 addCallback 均可準確的執行回調。這里就不得不說在調用addCallback的時候,其實將回到方法注冊到ListenableFutureCallbackRegistry(回調注冊中心)。

6.3.1 ListenableFutureCallbackRegistry的特性有:

a. 記錄了Callable的3種調度狀態:
NEW(新建,還未執行),SUCCESS(返回成功),FAILURE(拋異常,失敗)

b. 有兩個處理隊列,分別是:
successCallbacks:存儲執行成功的回調方法;
failureCallbacks: 存儲執行失敗的回調方法(拋異常)。

c. 存儲響應結果
將Callable<T> 返回的結果,也放在回調中心里面;

d. mutex 對象保證線程安全
有一個成員變量:private final Object mutex; 用來保證在添加回調任務,或者設置結果集的時候,注冊中心是線程安全的。

其在添加回調任務的時候,處理流程為: call調用時序

 synchronized(this.mutex) {...}

如上圖所示,在添加回調任務的時候,會通過synchronized先獲取 mutex 排它鎖,保證處理的線程安全。繼而判斷任務的狀態:

如果為NEW,標識任務還未執行完畢,這時候需要將任務先放入隊列,待任務執行完畢再根據狀態調用回調方法;

如果為SUCCESS,標識任務已經執行成功,不需要再放入隊列,而是在當前線程中,直接調用 onSuccess方法;

如果為FAILURE, 標識任務已經執行失敗,不需要再放入隊列,而是在當前線程中,直接調用 onFailure方法;

此外,還有一個流程,即在Callable任務調度完成,返回結果后,如果未拋異常:對successCallbacks隊列中的方法進行逐個回調;如果拋出異常,對failureCallbacks隊列中的方法進行逐個回調。因流程較簡單,這里只是簡單說明。

6.4 說明

本文只分析到 ListenableFuture 的使用方式, CountDownLatch的調用時機和ListenableFuture 的特性。

因時間和篇幅有限,具體spring 或 guava在submitListenable 任務之后,內部處理邏輯并沒有闡述。感興趣的同學可以具體到源碼查看其內部實行邏輯。

轉載于:https://my.oschina.net/davidzhang/blog/1839206

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282863.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282863.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282863.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

我堅持三年了!

閱讀本文大概需要5分鐘。不知不覺&#xff0c;公眾號寫作已經持續了3年了。2019年11月底&#xff0c;心血來潮寫了第一篇文章&#xff0c;更多是為了復盤過去的一些工作經歷。在前幾天&#xff0c;讀者數突破了16萬&#xff0c;雖然這個數字相比那些頭部大號而言并不多&#xf…

關于Qt模態框總匯

轉載請注明出處&#xff1a;http://www.cnblogs.com/dachen408/p/7285710.html 父窗體為QMainWindow&#xff1b; 當子窗體為&#xff1a; 1.QWidget&#xff0c;需要設置 this->setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); this->setWindowModality(Qt::Win…

linux腳本打印循環次數,shell腳本編程基礎(3)——循環用法

本節索引&#xff1a;一、if、case條件判斷二、for、while及until循環三、循環控制語句continue、break、shift及select菜單四、信號捕捉trap在前面的基礎編程內容中&#xff0c;我們已經學習了shell腳本的順序執行及選擇執行&#xff0c;通過這兩種方式&#xff0c;可以幫我們…

RTSP服務器之————rtsp-server(輕量級RTSP / RTP流媒體服務器)

github&#xff1a;https://github.com/revmischa/rtsp-server 輕量級RTSP / RTP流媒體服務器

EF CORE 7 中的新功能:使用 ExecuteDelete 和 ExecuteUpdate 進行批量操作

原文鏈接&#xff1a;https://timdeschryver.dev/blog/new-in-entity-framework-7-bulk-operations-with-executedelete-and-executeupdate原文作者&#xff1a;tim_deschryver翻譯&#xff1a;沙漠盡頭的狼(谷歌翻譯加持)Entity Framework 7 包括一些已被要求的流行功能&#…

java 簡單json和對象相互轉換

2019獨角獸企業重金招聘Python工程師標準>>> package Fasterxml; import com.fasterxml.jackson.databind.ObjectMapper; import mode.User; import java.io.StringWriter; import java.util.ArrayList; import java.util.List;/*** maven...**<dependency>* …

暢想動畫制作的樂趣

為什么要制作動畫&#xff1f; 現在的營銷活動&#xff0c;用一個很簡單的圖片去吸引消費者已經遠遠不夠。想讓消費者創造GMV&#xff0c;肯定需要讓消費者覺得眼前一亮或是有視覺沖擊的東西&#xff0c;或者在動畫過程中提供更好的引導部分&#xff0c;比如紅包&#xff0c;引…

Linux的scan命令,linux的scan命令

linux下scan命令主要是以scanf的形式使用轉換符解析字符串&#xff0c;下面由秋天網 Qiutian.ZqNF.Com小編為大家整理了linux下scan命令的相關知識&#xff0c;希望對大家有幫助!linux的scan命令詳解scan - 以sscanf的形式使用轉換符解析字符串語法:scan string format ?varna…

Spring Cloud Gateway 原生支持接口限流該怎么玩

關于pig&#xff1a; 基于Spring Cloud、oAuth2.0開發基于Vue前后分離的開發平臺&#xff0c;支持賬號、短信、SSO等多種登錄&#xff0c;提供配套視頻開發教程。 關于 Spring Cloud Gateway SpringCloudGateway是Spring官方基于Spring 5.0&#xff0c;Spring Boot 2.0和Projec…

我的手機 不支持箭頭函數

不支持&#xff0c;要換成function的形式 轉載于:https://www.cnblogs.com/web-fusheng/p/7295901.html

中標麒麟linux卸載qt,國產化 銀河麒麟編譯Qt程序的問題匯總 | 阿拉燈

Run in terminal莫名奇妙軟件無法在QtCreator中運行或者調試&#xff0c;main函數都無法進入&#xff0c;QtCreator中一運行就崩潰&#xff0c;并跳到匯編界面&#xff0c;這多半和代碼沒什么關系&#xff0c;我這里是將項目->運行中的“Run in terminal”去掉勾選&#xff…

css3-13 如何改變文本框的輪廓顏色

css3-13 如何改變文本框的輪廓顏色 一、總結 一句話總結&#xff1a;outline使用和border很像&#xff0c;幾乎一模一樣&#xff0c;多了一個offset屬性 1、輪廓outline如何使用&#xff1f; 使用和border很像&#xff0c;幾乎一模一樣&#xff0c;多了一個offset屬性 18 …

ios添加設備真機測試,以及Undefined symbols for architecture x86_64:''錯誤

問題今天坑了好久&#xff0c;然后找了各種資料 添加設備這個直接去開發者中心添加一個設備進去就好&#xff0c;具體流程百度&#xff0c;第二個問題是屬于路徑不對或者是靜態庫沒有添加成功&#xff0c;項目可以看到&#xff0c;到時路徑找不到&#xff0c;你把靜態庫拖到桌面…

linux NF NR實例,awk內建變量示例詳解之NR、FNR、NF

NR表示從awk開始執行后&#xff0c;按照記錄分隔符讀取的數據次數&#xff0c;默認的記錄分隔符為換行符&#xff0c;因此默認的就是讀取的數據行數&#xff0c;NR可以理解為Number of Record的縮寫。在awk處理多個輸入文件的時候&#xff0c;在處理完第一個文件后&#xff0c;…

迷宮探索

/* 5 4 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 1 1 4 3 */#include<iostream>using namespace std;struct node {int x;//橫坐標int y;//縱坐標int f;//父親在隊列中的編號int s;//步數 };int main() {node que[2051];int a[51][51]{0};int book[51][51]{0};//定義一…

Kinect2.0獲取數據

最近事情真是多&#xff0c;今天抽空研究一下Kinec2.0的數據獲取&#xff01; 系統要求 https://developer.microsoft.com/en-us/windows/kinect/hardware-setup 系統環境 聯想Y430P&#xff0c;Windows10 首先安裝了Kinect for Windows SDK &#xff08;KinectSDK-v2.0_1409-S…

linux超級工具,linux運維超級工具--sysdig

sysdig 是一個超級系統工具,它可以用來捕獲系統狀態信息&#xff0c;在運維工作中sysdig能很方便的排查異常、定位故障&#xff0c;它還能保存數據進行分析&#xff0c;并且提供強大的命令接口。在了解sysdig強大之處之前,首先得安裝sysdig&#xff0c;我這里是環境是centos6.7…

《sql語句練習1》

Oracle系列《一》&#xff1a;簡單SQL與單行函數 使用scott/tiger用戶下的emp表和dept表完成下列練習&#xff0c;表的結構說明如下 emp員工表(empno員工號/ename員工姓名/job工作/mgr上級編號/hiredate受雇日期/sal薪金/comm傭金/deptno部門編號) dept部門表(deptno部門編號…

Asp.net mvc 知多少(一)

本系列主要翻譯自《ASP.NET MVC Interview Questions and Answers 》- By Shailendra Chauhan&#xff0c;想看英文原版的可訪問http://www.dotnettricks.com/free-ebooks自行下載。該書主要分為兩部分&#xff0c;ASP.NET MVC 5、ASP.NET WEB API2。本書最大的特點是以面試問答…

stm32h7能跑linux,STM32H7榨干了Cortex-M7的最后一滴血

原標題&#xff1a;STM32H7榨干了Cortex-M7的最后一滴血有個非常重磅的消息ST給自己的STM32家族又新增了一條新的產品線—— H7H 代表的是High Pefrmance之意 (此為筆者臆測)7 則表示這是基于ARM Cortex-M7架構修改而來熟悉的工程師可能會問&#xff0c;不是已經有基于M7架構的…