[深入理解Java虛擬機器]第十二章 Java記憶體模型與執行緒-硬體的效率與一致性

Coding-lover發表於2015-11-07

概述

多工處理在現代計算機作業系統中幾乎已是一項必備的功能了。在許多情況下,讓計算機同時去做幾件事情,不僅是因為計算機的運算能力強大了,還有一個很重要的原因是計算機的運算速度與它的儲存和通訊子系統速度的差距太大,大量的時間都花費在磁碟I/O、網路通訊或者資料庫訪問上。如果不希望處理器在大部分時間裡都處於等待其他資源的狀態,就必須使用一些手段去把處理器的運算能力“壓榨”出來,否則就會造成很大的浪費,而讓計算機同時處理幾項任務則是最容易想到、也被證明是非常有效的“壓榨”手段。

除了充分利用計算機處理器的能力外,一個服務端同時對多個客戶端提供服務則是另一個更具體的併發應用場景。衡量一個服務效能的高低好壞,每秒事務處理數(Transactions Per Second,TPS)是最重要的指標之一,它代表著一秒內服務端平均能響應的請求總數,而TPS值與程式的併發能力又有非常密切的關係。對於計算量相同的任務,程式執行緒併發協調得越有條不紊,效率自然就會越高;反之,執行緒之間頻繁阻塞甚至死鎖,將會大大降低程式的併發能力。

服務端是Java語言最擅長的領域之一,這個領域的應用佔了Java應用中最大的一塊份額,不過如何寫好併發應用程式卻又是服務端程式開發的難點之一,處理好併發方面的問題通常需要更多的編碼經驗來支援。幸好Java語言和虛擬機器提供了許多工具,把併發程式設計的門檻降低了不少。並且各種中介軟體伺服器、各類框架都努力地替程式設計師處理儘可能多的執行緒併發細節,使得程式設計師在編碼時能更關注業務邏輯,而不是花費大部分時間去關注此服務會同時被多少人呼叫、如何協調硬體資源。無論語言、中介軟體和框架如何先進,開發人員都不能期望它們能獨立完成所有併發處理的事情,瞭解併發的內幕也是成為一個高階程式設計師不可缺少的課程。

Amdahl定律通過系統中並行化與序列化的比重來描述多處理器系統能獲得的運算加速能力,摩爾定律則用於描述處理器電晶體數量與執行效率之間的發展關係。這兩個定律的更替代表了近年來硬體發展從追求處理器頻率到追求多核心並行處理的發展過程。

硬體的效率與一致性

在正式講解Java虛擬機器併發相關的知識之前,我們先花費一點時間去了解一下物理計算機中的併發問題,物理機遇到的併發問題與虛擬機器中的情況有不少相似之處,物理機對併發的處理方案對於虛擬機器的實現也有相當大的參考意義。

“讓計算機併發執行若干個運算任務”與“更充分地利用計算機處理器的效能”之間的因果關係,看起來順理成章,實際上它們之間的關係並沒有想象中的那麼簡單,其中一個重要的複雜性來源是絕大多數的運算任務都不可能只靠處理器“計算”就能完成,處理器至少要與記憶體互動,如讀取運算資料、儲存運算結果等,這個I/O操作是很難消除的(無法僅靠暫存器來完成所有運算任務)。由於計算機的儲存裝置與處理器的運算速度有幾個數量級的差距,所以現代計算機系統都不得不加入一層讀寫速度儘可能接近處理器運算速度的快取記憶體(Cache)來作為記憶體與處理器之間的緩衝:將運算需要使用到的資料複製到快取中,讓運算能快速進行,當運算結束後再從快取同步回記憶體之中,這樣處理器就無須等待緩慢的記憶體讀寫了。

基於快取記憶體的儲存互動很好地解決了處理器與記憶體的速度矛盾,但是也為計算機系統帶來更高的複雜度,因為它引入了一個新的問題:快取一致性(Cache Coherence)。在多處理器系統中,每個處理器都有自己的快取記憶體,而它們又共享同一主記憶體(Main Memory),如圖12-1所示。當多個處理器的運算任務都涉及同一塊主記憶體區域時,將可能導致各自的快取資料不一致,如果真的發生這種情況,那同步回到主記憶體時以誰的快取資料為準呢?為了解決一致性的問題,需要各個處理器訪問快取時都遵循一些協議,在讀寫時要根據協議來進行操作,這類協議有MSI、MESI(Illinois Protocol)、MOSI、Synapse、Firefly及Dragon Protocol等。在本章中將會多次提到的“記憶體模型”一詞,可以理解為在特定的操作協議下,對特定的記憶體或快取記憶體進行讀寫訪問的過程抽象。不同架構的物理機器可以擁有不一樣的記憶體模型,而Java虛擬機器也有自己的記憶體模型,並且這裡介紹的記憶體訪問操作與硬體的快取訪問操作具有很高的可比性。

圖 12-1 處理器、快取記憶體、主記憶體間的互動關係

除了增加快取記憶體之外,為了使得處理器內部的運算單元能儘量被充分利用,處理器可能會對輸入程式碼進行亂序執行(Out-Of-Order Execution)優化,處理器會在計算之後將亂序執行的結果重組,保證該結果與順序執行的結果是一致的,但並不保證程式中各個語句計算的先後順序與輸入程式碼中的順序一致,因此,如果存在一個計算任務依賴另外一個計算任務的中間結果,那麼其順序性並不能靠程式碼的先後順序來保證。與處理器的亂序執行優化類似,Java虛擬機器的即時編譯器中也有類似的指令重排序(Instruction Reorder)優化。

相關文章