在Java并發實現的機制中,大部分的容器和框架都是依賴于volatile/synchronized/原子操作實現的,了解底層的并發機制,對于并發編程會帶來很多幫助
1. synchronized的應用
synchronized在多線程并發編程中已經是一個元老級的存在,通常被稱作是重量級鎖。既然是常用的一種鎖,那么就需要對它的底層實現有深入的了解。
1. synchronized的實現原理
當一個線程在訪問同步代碼塊時,就必須要先獲取該代碼塊中對象的鎖,退出或者拋出異常時,就必須要釋放鎖。synchronized的同步實現,是JVM基于進入和退出同步對象的Monitor對象來實現方法同步和代碼塊同步的。
每個Monitor對象都有與之關聯的monitor,當且僅當monitor被持有后,它才會處于鎖定狀態。同步代碼是使用monitorenter和monitorexit指令實現的。monitorenter指令時在代碼編譯后插入到同步代碼塊的開始位置,monitorexit指令則是插入到方法的結束和異常處。當線程執行到monitorenter時,會嘗試去獲取對象的monitor的所有權,即嘗試獲取對象的鎖。
2. synchronized的使用形式及意義
- 修飾普通方法:鎖是當前實例對象
- 修飾靜態方法:鎖是當前類的Class對象
- 修飾代碼塊:鎖是synchronized括號里面配置的對象
3. 鎖的升級
鎖共有四種狀態:無鎖狀態->偏向鎖狀態->輕量級鎖狀態->重量級鎖狀態
偏向鎖:當線程訪問同步塊并獲取鎖時,會在對象頭和棧針中的鎖記錄中存儲鎖偏向的ID,以后該線程在進入和退出同步塊時就不需要在使用CAS操作來進行加鎖和解鎖操作,而僅僅需要測試一下對象頭中的Mark Word字段中存儲的線程ID是否是當前線程即可。如果是,那么表示獲取鎖成功;如果不是,則需要檢查對象頭中偏向鎖的字段是否設置為了1(表示當前鎖是偏向鎖),如果沒有設置,則需要使用CAS來競爭獲取鎖,如果已經設置了,則嘗試使用CAS將對象頭的偏向鎖ID指向當前線程。
輕量級鎖;
- 輕量級鎖加鎖:線程在執行同步塊之前,首先會在自己的線程棧中創建一個用于存儲鎖記錄的空間,并將對象中的Mark Word 賦值到鎖記錄中。然后線程嘗試使用CAS操作將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,則表示當前線程獲取鎖,如果失敗,則表示其他線程也在競爭鎖,當前線程便使用自循的方式來獲取鎖。
- 輕量級鎖解鎖:輕量級鎖解鎖時,會使用原子CAS操作將鎖記錄替換會對象頭,如果成功,則表示沒有競爭發生。如果失敗,則表示當前鎖存在競爭,鎖就會膨脹為重量級鎖。
4. 鎖的對比
鎖類型 | 優點 | 缺點 | |
---|---|---|---|
重量級鎖 | 線程競爭不使用自旋,不會浪費CPU | 線程阻塞,響應時間慢 | 追求吞吐量,同步塊執行的時間長的場景 |
輕量級鎖 | 競爭的線程不會阻塞,提高程序的響應速度 | 如果線程長時間得不到鎖,那么自旋就會浪費CPU | 追求響應時間,同步塊執行速度快 |
偏向鎖 | 加鎖和解鎖不需要額外的資源消耗 | 如果線程間存在競爭,會帶來額外的鎖撤銷的消耗 | 適用于只有一個線程訪問同步場景 |
2. volatile的應用
volatile是輕量級的synchronized,volatile在多線程開發中保證了共享變量的可見性,所謂可見性,指的是當一個線程修改了共享變量之后,對于其他線程來說,能夠讀到這個修改的值。
1. volatile的定義及實現原理
volatile定義:Java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致地更新,線程應該確保通過排他鎖獲得這個變量。
實現原理:當對聲明了volatile的變量進行寫操作時,JVM就會向處理器發送一條帶有lock前綴的指令,將這個變量所在的緩存行的數據寫回到系統內存中。同時,在多處理器的情況下,需要執行緩存一致性協議,即每個處理器都需要通過嗅探總線上傳播的數據來檢查自己的緩存是否過期,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置為無效,當處理器需要對這個數據進行操作時,再從系統內存中把數據讀取到緩存行中。
2. 實現volatile的兩條原則
- 帶有Lock前綴的指令會引起處理器緩存寫回到內存;
- 一個處理器的緩存寫回到內存,會導致其他處理器的緩存無效。