Java基礎(chǔ)—并發(fā)包-底層原理

并發(fā)編程挑戰(zhàn)

?并發(fā)和串行,并發(fā)不一定比串行快,在不超過百萬次操作時(shí),并發(fā)比串行慢,原因在于線程有創(chuàng)建,上下文切換的時(shí)間開銷。

減少上下文切換:1. 無鎖并發(fā),2CAS算法,3使用最少線程,4單線程多任務(wù)調(diào)度。

jstack 3117 >/home/lemon/dump14 ?導(dǎo)出所有線程的信息,進(jìn)行JVM調(diào)優(yōu)可用。

避免死鎖的常見方法

1.避免一個(gè)線程同時(shí)獲取多個(gè)鎖,包含重入鎖。

2.避免一個(gè)線程占用多個(gè)資源,盡量一個(gè)鎖只占用一個(gè)資源

3.使用定時(shí)鎖,lock.tryLock(timeout) 代替內(nèi)部鎖機(jī)制。

4.有數(shù)據(jù)庫鎖,加鎖,解鎖都放在一個(gè)數(shù)據(jù)庫連接中。

Volatile與Synchronized

volatile : 原理與過程:屬性被修飾后,經(jīng)過編譯會(huì)出現(xiàn)兩個(gè)匯編代碼指令。這個(gè)指令會(huì)做兩件事。1.將當(dāng)前處理的屬性數(shù)據(jù)寫回系統(tǒng)內(nèi)存中【原理:多處理器會(huì)使用緩存一致性機(jī)制,鎖住緩存,確保修改一致性,同時(shí)也叫緩存鎖定】。2.將其他處理器的緩存無效?!驹恚禾幚砥魇褂肕ESI協(xié)議維護(hù)內(nèi)部緩存和其他處理的緩存一致性。一旦共享的內(nèi)存地址改變,那么嗅探器使其他緩存失效。下次讀取直接從內(nèi)存中撈取】

volatile優(yōu)化: 部分處理器高速緩存行是64字節(jié)寬,而LinkedTransferQueue這個(gè)出棧入棧類,將共享變量 追加到64個(gè)字節(jié),這樣填滿高速緩沖區(qū)的緩沖行,避免頭結(jié)點(diǎn)和尾節(jié)點(diǎn)加載到同一個(gè)緩存行,這樣避免修改的時(shí)候,因?yàn)榫彺嬉恢滦?,把頭部和尾部都鎖住。當(dāng)然如果緩存行不是64字節(jié)的不適用,共享變量不是平凡更改的也不使用。

Synchronized: ?1. 用在普通方法,鎖的是當(dāng)前實(shí)例對(duì)象。2.用在靜態(tài)同步方法,所得是類的class對(duì)象。3.用在同步方法塊,鎖的是synchronized括號(hào)的對(duì)象。

Synchronized: JVM在編譯代碼后,在同步代碼開始位置插入monitorenter,在結(jié)束位置插入monitorexit。必須是一對(duì)對(duì)的,當(dāng)線程執(zhí)行到monitorenter就開始嘗試獲取對(duì)象的鎖。鎖是存在java對(duì)象頭中。


頭長度和作用


鎖的升級(jí)

在JDK1.6以后,鎖升級(jí)為: 無鎖狀態(tài)--偏向鎖狀態(tài)--輕量級(jí)鎖--重量級(jí)鎖, 可升不能降。目的是提高獲得鎖和釋放鎖的效率。

偏向鎖:當(dāng)一個(gè)線程訪問同步塊時(shí),對(duì)象頭在mark Word里記錄線程的ID,以后線程進(jìn)入退出不需要CAS加鎖,解鎖,只需要判斷markWord是否有當(dāng)前線程即可,如果有,則獲取鎖,沒有則看markWord標(biāo)識(shí)是否設(shè)置成1,有則嘗試使用CAS指向自己,沒有則CAS競爭鎖。線程1代表初始化,線程2代表撤銷偏向鎖。

初始化和撤銷偏向鎖

輕量級(jí)鎖 : JVM 先創(chuàng)建一個(gè)空間存放鎖記錄,然后先將對(duì)象頭中的markWord復(fù)制到鎖記錄。然后CAS修改mark Word指針,如果成功則獲取鎖,失敗則表示有并發(fā)競爭,使用自旋獲取。 ?解鎖: CAS將原來復(fù)制的那份markWord換回對(duì)象頭,成功則解鎖,失敗則鎖變?yōu)橹亓考?jí)鎖。這個(gè)時(shí)候只有等待釋放鎖后,喚醒其他線程,然后再爭奪鎖。

對(duì)比
對(duì)比


原子操作及其原理

緩存行: 緩存中最小的操作單位。

比較并交換: compare and swap. 大名鼎鼎的CAS。操作時(shí),先輸入舊值,沒變化就替換,變化了則不替換。

處理器通過對(duì)緩存或總線加鎖保證內(nèi)存操作的原子性?;诳偩€鎖:【原理:處理器提供LOCK#信號(hào),當(dāng)有處理器發(fā)出這個(gè)信號(hào),其他處理器就阻塞,保證原子性?!??;诰彺骀i:【總線鎖開銷很多,因?yàn)榘袰PU和內(nèi)存都鎖住了,在高速緩存行中發(fā)出Lock#信號(hào),并使用緩存一致性機(jī)制阻止兩個(gè)以上處理器緩存往內(nèi)存寫,并使內(nèi)存行無效。類似volatile過程?!?/p>

CAS總結(jié)

? ? ? ? ?ABA問題: 一個(gè)值為A,然后變?yōu)锽,再變成A, 根據(jù)CAS,對(duì)比發(fā)現(xiàn)值沒變,但實(shí)際是變了,解決方案是追加版本號(hào),例如Atomic包中的AtomcStampedReference解決ABA。

循環(huán)時(shí)間開銷大:如果自旋CAS長時(shí)間不成功,CPU會(huì)開銷大,JVM支持處理器的pause指令則可以延遲流水線執(zhí)行指令,避免退出循環(huán)時(shí),因?yàn)轫樞驔_突,引起CPU流水線清空。

只能保證一個(gè)共享變量原子操作。 無法保證多個(gè),要么變成一個(gè)變量,要么用鎖。

總之: CAS為是一種樂觀鎖, synchronized是獨(dú)占式鎖,悲觀鎖。CAS提升了部分效率。

緩存一致性協(xié)議(MESI協(xié)議)

所有的服務(wù)器運(yùn)行的原理是, 進(jìn)程中運(yùn)行某一個(gè)線程的時(shí)候,會(huì)從主存中復(fù)制一份數(shù)據(jù)到自己的工作空間,也就是告訴緩存區(qū)中,然后開始業(yè)務(wù)操作和數(shù)據(jù)修改,然后再將這個(gè)數(shù)據(jù)返回到主內(nèi)存中。然后清空自己的工作區(qū)間。


如果同時(shí)在兩個(gè)處理器進(jìn)行處理, 就會(huì)出現(xiàn)緩存不一致的情況, 這個(gè)時(shí)候有兩個(gè)方案解決,一個(gè)是基于LOCK#信號(hào)鎖住總線,上面有講述。 這樣會(huì)導(dǎo)致CPU運(yùn)行效率差。

比較好的第二種方法則是 緩存一致性協(xié)議:當(dāng)CPU寫數(shù)據(jù)時(shí),如果發(fā)現(xiàn)操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會(huì)發(fā)出信號(hào)通知其他CPU將該變量的緩存行置為無效狀態(tài),因此當(dāng)其他CPU需要讀取這個(gè)變量時(shí),發(fā)現(xiàn)自己緩存中緩存該變量的緩存行是無效的,那么它就會(huì)從內(nèi)存重新讀取。

鎖機(jī)制

線程通信

線程A,將本地內(nèi)存值刷新到共享內(nèi)存中, 其他線程,在主內(nèi)存中獲取最新值,更新到自己本地內(nèi)存中。

重排序

有時(shí)候?yàn)榱颂岣咝阅?,編譯器和處理器會(huì)對(duì)指令進(jìn)行重新排序。大致分為以下三類

1.編譯器優(yōu)化重排序。可以改變語句的執(zhí)行順序。

2. 指令級(jí)并行的重排序。處理器將多條指令重疊執(zhí)行,如沒依賴性,處理器改變語句對(duì)應(yīng)的機(jī)器指令執(zhí)行順序。

3. 內(nèi)存系統(tǒng)重排序。

模型

jdk5以后,均采用JSR-133模型,且使用happends-before概念。前一個(gè)操作,對(duì)后一個(gè)操作可見。 順序一致性內(nèi)存模型

參考大神圖書《Java并發(fā)編程藝術(shù)》總結(jié)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容