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