事先聲明,我只是java併發的新手,這篇文章也只是我閱讀《java併發編程的藝術》一書(內容主要涉及前3章)的一些總結和感悟。希望大家能多多討論,對於錯誤的地方還請指出。 0. 簡介 程式的世界是有層次分明的,每層都對外封裝細節而提供一些方式或者說介面來提供功能,甚至是約束功能來換取正確性等等。那麼 ...
事先聲明,我只是java併發的新手,這篇文章也只是我閱讀《java併發編程的藝術》一書(內容主要涉及前3章)的一些總結和感悟。希望大家能多多討論,對於錯誤的地方還請指出。
0. 簡介
程式的世界是有層次分明的,每層都對外封裝細節而提供一些方式或者說介面來提供功能,甚至是約束功能來換取正確性等等。那麼接下來,就用分層的思想作為靈魂,各種記憶體模型作為骨架,來簡單討論Java併發的原理。
1. 處理器記憶體模型
我們面對的第一個問題是,一個好的處理器要滿足什麼條件?就像考試的學生一樣,當然是做得又快,對得又多。對於處理器來說一般要滿足兩個條件:
1. 效率
2. 正確性
1.1 效率
對於效率來說,處理器提供了很多的機制來滿足這一點,並行,指令流水線,指令重排,緩存機制等等。但這些方式必須加以限制來保證正確性,或者說效率和正確性在一定程度上是相互制約的,我們必須加以權衡,合理折中。
1.2 正確性
那麼怎麼保證正確性呢?同樣除了處理器的一些內部的機制,比如禁止某些指令重排,緩存一致性,匯流排鎖,緩存鎖定等等,同時還向其上層提供了好的方法,比如說記憶體屏障,CAS等等,這些方法、機制會讓上層(通過合理的方式)更好的來保證程式的正確性,就像Java會提供volatile,synchronized等等方式來讓程式員(通過合理的方式)調用保證程式的線程安全。
讓我們更加深入這個話題,一個程式怎麼樣才叫正確?要滿足什麼性質?簡單來說,有三條性質:
1. 原子性,就是要麼全部做要麼全不做,需要硬體的配合,比如CAS。
2. 可見性,由於緩存的存在,要讓一個處理器處理的結果對其他處理器可見,首先要把緩存寫入記憶體。
3. 順序性,程式必須保持一定的順序依次的產生結果。
註意,處理器的很多機制可以保持其中多種性質,比如記憶體屏障就可以把緩存刷新進入記憶體,而其他處理器的緩存無效來保證可見性,和通過禁止指令重排來保證順序性。比如匯流排鎖,緩存鎖定,緩存一致性就通過各種方式來保證順序性和原子性。
2. java記憶體模型
好了我們更上一層樓,我們剛剛說在下一層(處理器)提供了各種機制來保證效率和正確性,而這一層正是要使用這些機制來到達這一層,也就是JRE和編譯器的正確性和效率。
JMM(即java記憶體模型)同處理器模型一樣,也有很多的機制,包括各種優化。對外也提供volatile,synchronized和臭名昭著的concurrent包。上層的程式員也要使用(合理的方式)這些Java語言和包來保證線程安全。以下簡單敘述一些本層次與上一層次的依賴(註意,synchronized因為JMM的優化可以分為3種)
1. final域 -> 記憶體屏障
2. volatile -> 記憶體屏障(位元組碼層次)-> 記憶體屏障(處理器層次)
3. synchronized -> 偏向鎖 + 輕量級鎖 + 互斥鎖
偏向鎖 -> CAS
輕量級鎖 -> CAS
互斥鎖 -> moniter -> 原子級處理器指令
4. concurrent包 -> volatile + CAS
註意CAS實現concurrent包中的原子操作可能會有三大問題:
1. ABA問題。(解決辦法:在變數前增加版本號)
2. 迴圈時間長開銷大。(解決辦法:pause指令)
3. 只能保證一個共用變數的原子操作。(解決辦法:用鎖或著合併操作)
JMM把各種控製程序順序性的機制抽象成文字形式,就是happens-before。其目的就是向Java開發者屏蔽特定平臺的底層細節,設計的程式只要遵守happens-before就可以保證正確性。
3. 程式員
這是最上面的層次,簡單來說就是來利用好下層提供的機制來更好的編程。我想這也是我們要學習java併發原理的原因。