JVM 執行緒池發展趨勢

ImportNew發表於2015-04-08

多執行緒已經成為大多數開發者的興趣所在了。他們努力嘗試想找出最優策略來解決這個問題。過去已經有各種嘗試去標準化這些方案。特別是隨著大資料,實時分析等問題領域的興起,又存在著新的挑戰。在這個方向需要走的一步是“Doug Lea”的作品(一部鉅作),以併發框架(JSR 166)的形式提供給我們。

現在開始區分併發和並行性。這些只是不同的策略,而且市面上有很多框架提供,都能幫我們達到相同的目的。但在選擇的時候如果能同時知道他們內部的實現細節對我們也是大有好處的。本文將要探究JVM執行緒池和執行緒共享的一些穩定有效的選項。當然,隨著多核處理器的廣泛使用,新的問題也隨之而來。開發人員也開始思考利用高階硬體的“mechanical sympathy”(譯者注:表示底層硬體的運作方式以及與硬體執行方式協同的軟體程式設計)來提高效能。

個人以為,當討論執行緒池時,目前廣泛應用的主要有下述機制:

  1. Executor框架提供的執行緒。
  2. LMAX的Ring Buffer概念 (譯者注:Ring Buffer即環形緩衝,LMAX是一種新型零售金融交易平臺,其框架能以低延遲產生大量交易,LMAX建立在JVM平臺上。
  3. 基於Actor(事件)的實現。

併發框架下的執行緒池選項:

首先,我個人不贊同使用當下流行的執行緒池概念,而應該使用工作佇列的概念。簡而言之,在一個執行框架可供選擇的各種不同選項都是基於某種順序資料結構,如陣列或佇列(阻塞或非阻塞)之類的,比如ConcurrentLinkedQueue(併發鏈式佇列),ArrayBlockingQueue(陣列阻塞佇列), LinkedBlockingQueue(鏈式阻塞佇列)等等。文件表明,儘管它們的使用環境各不相同,但他們隱含的本質/資料結構有相同的性質,如順序插入和遍歷。

JVM執行緒池發展趨勢

優勢:

  1. 減少執行緒建立導致的延遲
  2. 通過優化執行緒數量,可以解決資源不足的問題。

這些可以使應用程式和伺服器應用響應更快。使用執行緒池看似一個很不錯的解決方案但是卻有一個根本性的缺陷:連續爭用問題。這裡是Java中關於一些併發框架下執行緒池選項的討論。

Disruptor(環形緩衝):

(LMAX的一個基於環形緩衝區的高效能程式間訊息庫)LMAX的開發人員使用一個干擾框架來解決連續爭用問題,這個框架是基於一個叫環形緩衝的資料結構。它可能是執行緒間傳送訊息的最有效方式了。它是佇列的一種替代實現方式,但它又和SEDA和Actors(譯者注:這兩種都是和Disruptor類似的併發模型)有一些共同特徵。向Disruptor中放入訊息需要兩步,第一步申請一個環形緩衝的槽位,槽位可為使用者提供寫對應資料的記錄。然後需要提交該條記錄,為了能靈活使用記憶體,2步法是必須的。只有經過提交,這條訊息才能對消費者執行緒可見。下圖描述了環狀緩衝這個資料結構(Disruptor的核心):

譯者注:LMAX的核心是一個業務邏輯處理器,而該業務邏輯處理器的核心是Disruptor,這是一個併發元件,能夠在無鎖的情況下實現網路的Queue併發操作

JVM執行緒池發展趨勢

Disruptor在多核平臺上能達到很低的延遲同時又有高吞吐量,儘管執行緒程間需要共享資料以及傳遞訊息。

它的獨特之處在於鎖和免爭用結構。它甚至不使用CAS或記憶體保護。若想了解關於它的更多細節,這裡有一篇不錯的文章官網。使用Disruptor的一個缺點(事實上也算不上缺點)是,你需要提前告知Disruptor應用程式完成任務所需要的執行緒數。

基於事件:

對於傳統執行緒池機制,一個強大的替代方案就是基於事件模型。這種基於事件的執行緒輪詢/執行緒池/執行緒排程機制在函數語言程式設計中很常見。關於這個概念的一個非常流行的實現是基於actor的系統(譯者注:Scala的併發系統),Akka已成為其實際上的標準。(譯者注:Akka,一種善於處理程式間通訊的框架

Actors是非常輕量級的併發實體。它們使用一種事件驅動的接收迴圈來非同步處理訊息。訊息模式匹配可以很方便地表述一個actor的行為。它們提高了抽象級別從而使寫,測試,理解和維護併發/分散式系統更加容易。讓你專注於工作流——訊息如何流入系統——而不是低層次的基本概念如執行緒,鎖以及套接字IO。一個執行緒可以分配多個或單個actor,而且兩種模型都是依需要選擇的。

像Akka這種基於actor的系統的優勢有如下所列:

  • 可封裝
  • 可監督
  • 可配置執行
  • 位置透明
  • 重試機制

注:除錯一個基於actor的系統是一個非常艱難的事情。

Disruptor使用一個執行緒一個消費者模式,不同於Actors使用N個執行緒M個消費者模式。比如,你可以擁有任意多的actors,然後它們會被分散到一些數目固定的執行緒中(通常是一個核一個執行緒),至於其他的部分,actor模型就和Disruptor模型差不多了;特別是用於批處理的時候。

我最初在因特網上搜多到的答案也說明開源空間中關於確定JVM選項基準的貢獻還是有一些的。其中一個選項是ExecutorBenchmarkt。它是一個並行任務的開源測試框架。它是用Scala編寫的,但是可以用於Java和Scala負載。

簡而言之,快速發展的軟硬體行業在呈現新挑戰給我們的同時也提供了大量解決應用程式容錯性和響應性的方法。對於不可預知的少量執行緒,我建議使用JDK併發框架中的執行緒池機制。對於大量規模相似的任務,個人建議使用Disruptor。Disruptor的確是有一點學習曲線,但在效能和擴充套件性方面的收穫遠遠值得投入的學習時間。在應用程式需要某種重試或管理機制,以及分散式任務時,建議使用Akka的Actor模型。儘管結果還有可能被其它因素所影響,你還是會選擇map reduce或fork/join模型或是其它自定義實現一個分散式應用程式。

相關文章