确保任务的执行顺序
有時有必要對線程池中的任務(wù)施加一定的順序。 JavaSpecialists通訊的第206期提出了一種這樣的情況:我們有多個連接,使用NIO可以從中讀取。 我們需要確保給定連接中的事件按順序執(zhí)行,但是不同連接之間的事件可以自由混合。
我想提出一個類似但略有不同的情況:我們有N個客戶。 我們希望按照提交順序執(zhí)行給定客戶端的事件,但是來自不同客戶端的事件可以自由混合。 另外,有時還會有涉及多個客戶端的“匯總”任務(wù)。 此類任務(wù)應(yīng)阻止所有相關(guān)客戶端的任務(wù)(但不能阻止更多任務(wù)!)。 讓我們看一下情況圖:
如您所見,來自客戶端A和客戶端B的任務(wù)被并行地愉快地處理,直到出現(xiàn)“匯總”任務(wù)。 到那時,不能再處理類型A或B的任務(wù),但是可以執(zhí)行無關(guān)的任務(wù)C(前提是有足夠的線程)。 我的存儲庫中提供了這種執(zhí)行程序的框架。 核心是以下界面:
public interface OrderedTask extends Runnable {boolean isCompatible(OrderedTask that); }使用此接口, A.isCompatible(B) && B.isComaptible(A)確定兩個任務(wù)是否可以并行運行(如果A.isCompatible(B) && B.isComaptible(A)則A和B可以并行運行)。 這些方法應(yīng)以快速,非鎖定和時不變的方式實現(xiàn)。
該線程池背后的算法如下:
- 如果要添加的任務(wù)與任何現(xiàn)有任務(wù)不沖突,請將其添加到元素最少的線程中。
- 如果它與來自一個線程的元素沖突,則安排它在該線程上執(zhí)行(并隱式地在沖突元素之后執(zhí)行,以確保提交順序得以維持)
- 如果它與多個線程沖突,則在第一個線程上等待任務(wù)的第一個線程之外的所有任務(wù)上添加任務(wù)(下面用紅色顯示),然后在該任務(wù)上執(zhí)行原始任務(wù)。
有關(guān)實現(xiàn)的更多信息:
- 該代碼僅是概念驗證,還需要更多代碼才能使其具有生產(chǎn)質(zhì)量(它需要代碼來執(zhí)行任務(wù)中的異常處理,正確關(guān)閉等)。
- 為了獲得最佳性能,它使用可用的無鎖*結(jié)構(gòu):每個工作線程都有一個關(guān)聯(lián)的ConcurrentLinkedQueue。 為了達到睡眠直到工作可用的語義,使用了額外的信號量**
- 為了能夠?qū)⑿碌腛rderedTask與當前正在執(zhí)行的OrderedTask進行比較,請保留其引用的副本。 每當新元素入隊時,此副本列表都會更新(這可能會導(dǎo)致內(nèi)存泄漏,并且如果任務(wù)不頻繁,則應(yīng)研究足夠的替代方法,例如為弱引用提供額外的計時器)
- 與JavaSpecialists時事通訊中的解決方案相比,這更類似于固定線程池執(zhí)行器,而時事通訊中的解決方案類似于緩存的線程池執(zhí)行器。
- 如果(a)任務(wù)(大部分)短且(大多數(shù))統(tǒng)一,并且(b)很少(一個或兩個)線程提交新任務(wù),則此實現(xiàn)是理想的,因為多個提交是互斥的(但是提交和執(zhí)行不是“ t)
- 如果在提交“匯總”之后(并且可以在執(zhí)行之前)立即提交相同類型的任務(wù),則不必要地將它們強制在一個線程上。 如果這成為一個問題,我們可以在匯總?cè)蝿?wù)完成后添加代碼重排任務(wù)。
盡情享受源代碼 ! (也許有一天我會花時間刪除所有粗糙的邊緣)。
*有點用詞不當,因為仍然有鎖,僅在較低級別(CPU而不是OS)級別上使用,但這是公認的術(shù)語
** –基準測試表明這是性能最高的解決方案。 這是從ThreadPoolExecutor的實現(xiàn)中得到啟發(fā)的。
參考:在Java Advent Calendar博客上, 確保 JCG合作伙伴 Attila-Mihaly Balazs 執(zhí)行任務(wù)的順序 。
翻譯自: https://www.javacodegeeks.com/2012/12/ensuring-the-order-of-execution-for-tasks.html
總結(jié)
- 上一篇: 奇富科技宣布参编三大金融国标
- 下一篇: 使用Flying-Saucer生成PDF