在Java并發(fā)實現(xiàn)的機制中剃诅,大部分的容器和框架都是依賴于volatile/synchronized/原子操作實現(xiàn)的伙狐,了解底層的并發(fā)機制,對于并發(fā)編程會帶來很多幫助
1. synchronized的應(yīng)用
synchronized在多線程并發(fā)編程中已經(jīng)是一個元老級的存在带饱,通常被稱作是重量級鎖邻奠。既然是常用的一種鎖,那么就需要對它的底層實現(xiàn)有深入的了解辜妓。
1. synchronized的實現(xiàn)原理
當一個線程在訪問同步代碼塊時枯途,就必須要先獲取該代碼塊中對象的鎖,退出或者拋出異常時籍滴,就必須要釋放鎖酪夷。synchronized的同步實現(xiàn),是JVM基于進入和退出同步對象的Monitor對象來實現(xiàn)方法同步和代碼塊同步的孽惰。
每個Monitor對象都有與之關(guān)聯(lián)的monitor晚岭,當且僅當monitor被持有后,它才會處于鎖定狀態(tài)勋功。同步代碼是使用monitorenter和monitorexit指令實現(xiàn)的坦报。monitorenter指令時在代碼編譯后插入到同步代碼塊的開始位置,monitorexit指令則是插入到方法的結(jié)束和異常處狂鞋。當線程執(zhí)行到monitorenter時片择,會嘗試去獲取對象的monitor的所有權(quán),即嘗試獲取對象的鎖骚揍。
2. synchronized的使用形式及意義
- 修飾普通方法:鎖是當前實例對象
- 修飾靜態(tài)方法:鎖是當前類的Class對象
- 修飾代碼塊:鎖是synchronized括號里面配置的對象
3. 鎖的升級
鎖共有四種狀態(tài):無鎖狀態(tài)->偏向鎖狀態(tài)->輕量級鎖狀態(tài)->重量級鎖狀態(tài)
偏向鎖:當線程訪問同步塊并獲取鎖時字管,會在對象頭和棧針中的鎖記錄中存儲鎖偏向的ID,以后該線程在進入和退出同步塊時就不需要在使用CAS操作來進行加鎖和解鎖操作信不,而僅僅需要測試一下對象頭中的Mark Word字段中存儲的線程ID是否是當前線程即可嘲叔。如果是,那么表示獲取鎖成功抽活;如果不是硫戈,則需要檢查對象頭中偏向鎖的字段是否設(shè)置為了1(表示當前鎖是偏向鎖),如果沒有設(shè)置酌壕,則需要使用CAS來競爭獲取鎖掏愁,如果已經(jīng)設(shè)置了,則嘗試使用CAS將對象頭的偏向鎖ID指向當前線程卵牍。
輕量級鎖果港;
- 輕量級鎖加鎖:線程在執(zhí)行同步塊之前,首先會在自己的線程棧中創(chuàng)建一個用于存儲鎖記錄的空間糊昙,并將對象中的Mark Word 賦值到鎖記錄中辛掠。然后線程嘗試使用CAS操作將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,則表示當前線程獲取鎖萝衩,如果失敗回挽,則表示其他線程也在競爭鎖,當前線程便使用自循的方式來獲取鎖猩谊。
- 輕量級鎖解鎖:輕量級鎖解鎖時千劈,會使用原子CAS操作將鎖記錄替換會對象頭,如果成功牌捷,則表示沒有競爭發(fā)生墙牌。如果失敗,則表示當前鎖存在競爭暗甥,鎖就會膨脹為重量級鎖喜滨。
4. 鎖的對比
鎖類型 | 優(yōu)點 | 缺點 | |
---|---|---|---|
重量級鎖 | 線程競爭不使用自旋,不會浪費CPU | 線程阻塞撤防,響應(yīng)時間慢 | 追求吞吐量虽风,同步塊執(zhí)行的時間長的場景 |
輕量級鎖 | 競爭的線程不會阻塞,提高程序的響應(yīng)速度 | 如果線程長時間得不到鎖寄月,那么自旋就會浪費CPU | 追求響應(yīng)時間辜膝,同步塊執(zhí)行速度快 |
偏向鎖 | 加鎖和解鎖不需要額外的資源消耗 | 如果線程間存在競爭,會帶來額外的鎖撤銷的消耗 | 適用于只有一個線程訪問同步場景 |
2. volatile的應(yīng)用
volatile是輕量級的synchronized剥懒,volatile在多線程開發(fā)中保證了共享變量的可見性内舟,所謂可見性合敦,指的是當一個線程修改了共享變量之后初橘,對于其他線程來說,能夠讀到這個修改的值充岛。
1. volatile的定義及實現(xiàn)原理
volatile定義:Java編程語言允許線程訪問共享變量保檐,為了確保共享變量能被準確和一致地更新,線程應(yīng)該確保通過排他鎖獲得這個變量崔梗。
實現(xiàn)原理:當對聲明了volatile的變量進行寫操作時夜只,JVM就會向處理器發(fā)送一條帶有l(wèi)ock前綴的指令,將這個變量所在的緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存中蒜魄。同時扔亥,在多處理器的情況下,需要執(zhí)行緩存一致性協(xié)議谈为,即每個處理器都需要通過嗅探總線上傳播的數(shù)據(jù)來檢查自己的緩存是否過期旅挤,當處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改,就會將當前處理器的緩存行設(shè)置為無效伞鲫,當處理器需要對這個數(shù)據(jù)進行操作時粘茄,再從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀取到緩存行中。
2. 實現(xiàn)volatile的兩條原則
- 帶有Lock前綴的指令會引起處理器緩存寫回到內(nèi)存;
- 一個處理器的緩存寫回到內(nèi)存柒瓣,會導(dǎo)致其他處理器的緩存無效儒搭。