該并發(fā)學(xué)習(xí)系列以閱讀《Java并發(fā)編程的藝術(shù)》一書的筆記為藍(lán)本辽聊,匯集一些閱讀過(guò)程中找到的解惑資料而成。這是一個(gè)邊看邊寫的系列股淡,有興趣的也可以先自行購(gòu)買此書學(xué)習(xí)身隐。
本文首發(fā):windCoder
處理器中的原子操作
原子操作意為:不可中斷的一個(gè)或一系列操作。現(xiàn)在先了解幾個(gè)相關(guān)術(shù)語(yǔ):
術(shù)語(yǔ)名稱 | 英文 | 解釋 |
---|---|---|
比較并交換 | Compare And Sqap | CAS操作需要輸入兩個(gè)數(shù)值唯灵,一個(gè)舊值(期望操作前的值)和一個(gè)新值贾铝,在操作期間先比較舊值有沒(méi)有發(fā)生變化,如果沒(méi)有發(fā)生變化,才交換新值垢揩,發(fā)生了變化則不交換玖绿。 |
CPU流水線 | CPU Pipeline | CPU流水線的工作方式就像工業(yè)生產(chǎn)上的裝配流水線,在CPU中由56個(gè)不同功能的電路單元組成一條指令處理流水線叁巨,然后將一條x86指令分成56步后再由這些電路單元分別執(zhí)行斑匪,這樣就能實(shí)現(xiàn)在一個(gè)CPU時(shí)鐘周期完成一條指令,因此提高CPU的運(yùn)算速度锋勺。 |
內(nèi)存順序周期 | Memory Order Violation | 內(nèi)存順序沖突一般是由假共享引起的蚀瘸,假共享是指多個(gè)CPU同時(shí)修改同一個(gè)緩存行的不同部分而引起其中一個(gè)CPU的操作無(wú)效,當(dāng)出現(xiàn)這個(gè)內(nèi)存順序沖突時(shí)庶橱,CPU必須清空流水線贮勃。 |
處理器使用對(duì)緩存加鎖或總線加鎖的方式來(lái)實(shí)現(xiàn)多處理器之間的原子操作。
處理器會(huì)自動(dòng)保證基本的內(nèi)存操作的原子性苏章。處理器保證從系統(tǒng)內(nèi)存中讀取或者寫入一個(gè)字節(jié)是原子的寂嘉,即:當(dāng)一個(gè)處理器讀取一個(gè)字節(jié)時(shí),其他處理器不能訪問(wèn)這個(gè)字節(jié)的內(nèi)存地址枫绅。
處理器不能自動(dòng)保證復(fù)雜的內(nèi)存操作的原子性泉孩。如:跨總線寬度、跨多個(gè)緩存行和跨頁(yè)表的訪問(wèn)等復(fù)雜內(nèi)存操作并淋。
處理器提供總線鎖定和緩存鎖定兩個(gè)機(jī)制來(lái)保證復(fù)雜內(nèi)存操作的原子性寓搬。
總線鎖定就是使用處理器提供的一個(gè)#LOCK信號(hào),當(dāng)一個(gè)處理器在總線程上輸出此信號(hào)時(shí)预伺,其他處理器的請(qǐng)求將被阻塞住订咸,那么該處理器可以獨(dú)占共享內(nèi)存。
總線鎖定把CPU和內(nèi)存之間的通信鎖住了酬诀,這使得鎖定期間脏嚷,其他處理器不能操作其他內(nèi)存地址的數(shù)據(jù),所以總線鎖定的開銷比較大瞒御,目前處理器在某些場(chǎng)合下使用緩存鎖定代替總線鎖定來(lái)進(jìn)行優(yōu)化父叙。
緩存鎖定是指內(nèi)存區(qū)域如果被緩存在處理器的緩存行中,并且在LOCK操作期間被鎖定肴裙,那么當(dāng)它執(zhí)行鎖操作回寫到內(nèi)存時(shí)趾唱,處理器不在總線上聲言#LOCK信號(hào),而是修改內(nèi)部的內(nèi)存地址蜻懦,并允許它的緩存一致性機(jī)制來(lái)保證操作的原子性甜癞。
該緩存一致性機(jī)制會(huì)阻止同時(shí)修改由兩個(gè)以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù),當(dāng)其它處理器回寫已被鎖定的緩存行的數(shù)據(jù)時(shí)宛乃,會(huì)使緩存行無(wú)效念秧。
但有兩種情況下處理器不會(huì)使用緩存鎖定:
- 1.當(dāng)操作的數(shù)據(jù)不能被緩存在處理器內(nèi)部,或操作的數(shù)據(jù)跨多個(gè)緩存行時(shí)峭状,處理器會(huì)調(diào)用總線鎖定茬斧。
- 2.有些處理器不支持緩存鎖定止吐。如Intel 486和Pentium處理器,就算鎖定的內(nèi)存區(qū)域在處理器的緩存行中也會(huì)調(diào)用總線鎖定。
Java中原子操作
Java中可以通過(guò)鎖和循環(huán)CAS的方式來(lái)實(shí)現(xiàn)原子操作。
使用循環(huán)CAS實(shí)現(xiàn)原子操作
JVM中使用的CAS操作利用了處理器提供的CMPXCHG
指令實(shí)現(xiàn)的拂玻。
自旋CAS實(shí)現(xiàn)的基本思想是循環(huán)進(jìn)行CAS操作直到成功為止。
從 Java 1.5開始宰译,JDK的并發(fā)包里提供了一些類來(lái)支持原子操作檐蚜,如AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)沿侈。這些原子包裝類還提供了有用的工具方法熬甚,如以原子的方式將當(dāng)前值自增1和自減1。
CAS實(shí)現(xiàn)原子操作有三大問(wèn)題:ABA問(wèn)題肋坚、循環(huán)時(shí)間長(zhǎng)開銷大、只能保證一個(gè)共享變量的原子性肃廓。
自旋CAS如果長(zhǎng)時(shí)間不成功智厌,會(huì)給CPU帶來(lái)非常大的執(zhí)行開銷。若JVM能支持處理器提供的pause指令盲赊,效率會(huì)有一定的提升铣鹏。
pause指令可以延遲流水線執(zhí)行指令(de-pipline),也可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(Memory Order Violation)而引起CPU流水線被清空(CPU Pipelne Flush)哀蘑,從而提高CPU的執(zhí)行效率诚卸。
更多CAS的知識(shí)可參考:CAS初探
使用鎖機(jī)制實(shí)現(xiàn)原子操作
鎖機(jī)制保證了只有獲得鎖的線程才能夠操作鎖定的內(nèi)存區(qū)域。
JVM內(nèi)部實(shí)現(xiàn)了很多種鎖機(jī)制绘迁,有偏向鎖合溺、輕量級(jí)鎖和互斥鎖。
除了偏向鎖缀台,JVM實(shí)現(xiàn)鎖的方法都使用了CAS棠赛,即當(dāng)一個(gè)線程想進(jìn)入同步塊的時(shí)候使用循環(huán)CAS的方式來(lái)獲取鎖,當(dāng)它退出同步塊時(shí)使用循環(huán)CAS釋放鎖膛腐。
Java 中大部分容器和框架依賴于 volatile 和原子操作的實(shí)現(xiàn)原理睛约,了解這些原理對(duì)我們進(jìn)行并發(fā)編程會(huì)很有幫助。