并發(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í)候只有等待釋放鎖后,喚醒其他線程,然后再爭奪鎖。


原子操作及其原理
緩存行: 緩存中最小的操作單位。
比較并交換: 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)存重新讀取。

線程通信
線程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é)。