2. Java并發(fā)機制的底層實現(xiàn)原理

1. volatile的應(yīng)用

volatile是輕量級synchronized, 保證了共享變量的可見性, 可見性的意思當(dāng)一個線程修改一個共享變量時, 其他線程能讀取到這個修改的值, volatile變量的使用比synchronized的成本更低, volatile關(guān)鍵字不會引起線程上下文切換和調(diào)度

1.1 volatile的定義與實現(xiàn)原理
術(shù)語 英文單詞 術(shù)語描述
內(nèi)存屏障 memory barries 一組CPU指令,用于實現(xiàn)對內(nèi)存操作的順序限制
CPU緩存 cache 緩存了內(nèi)存地址,以及對應(yīng)的數(shù)據(jù)
緩沖行 cache line CPU緩存可以分配的最小存儲單位,CPU修改一個緩存會影響整個緩存行
原子操作 atomic operations 不可中斷的一個或一系列操作
緩存行填充 cache line fill CPU從內(nèi)存中讀取的數(shù)據(jù)是可緩存的,CPU將數(shù)據(jù)緩存到L1,L2,L3的緩存行中
緩存命中 cache hit CPU直接從緩存中讀取數(shù)據(jù)
寫命中 write hit CPU將操作數(shù)再次寫回緩存行
寫缺失 write misses the cache CPU寫入的數(shù)據(jù),不在CPU緩存中
volatile如何保證內(nèi)存可見

為了提高處理速度, CPU不直接與內(nèi)存通信, 而是將內(nèi)存的數(shù)據(jù)讀到CPU緩存中再進行操作, 但操作完成之后, 不知道何時寫入到內(nèi)存

修改了volatile關(guān)鍵字修飾共享變量之后

  1. JVM會下發(fā)一個Lock指令到CPU, 將CPU緩存寫回到內(nèi)存
    • LOCK信號一般不鎖總線, 而是緩存鎖, 總線鎖開銷比較大
    • 鎖定內(nèi)存區(qū)域的緩存, 并將其寫回到內(nèi)存, 使用了緩存一致性機制來確保修改的原子性
    • 緩存一致性會阻止同時修改兩個以上CPU緩存的內(nèi)存區(qū)域數(shù)據(jù)
  2. 一個CPU的緩存寫回到內(nèi)存之后, 會導(dǎo)致其他處理器對于volatile共享變量的緩存無效
    • 使用MESI控制協(xié)議維護內(nèi)部緩存和其他CPU緩存一致性
    • MESI: 每個緩存行使用4種狀態(tài)進行標記
      • M: 被修改(Modified), 與內(nèi)存不一致, 緩存就需要在之后的某個時間點, 將數(shù)據(jù)寫回到內(nèi)存
      • E: 獨享(Exclusive), 緩存行被一個CPU緩存, 并且數(shù)據(jù)和內(nèi)存一致, 之后如果被其他CPU訪問, 那么就會變成共享的
      • S: 共享(Shared), 緩存行被多個CPU共享, 當(dāng)有一個CPU修改了緩存行, 緩存行就會被作廢, 變成無效狀態(tài)
      • I: 無效(Invalid), 緩存無效, 可能是其他CPU修改了緩存行
1.2 volatile的使用優(yōu)化

追加字節(jié)優(yōu)化性能, JDK1.8使用@Contended注解填充緩存行
- CPU使用緩存行有固定長度的, 例如64位, 256位
- 如果緩存對象沒有達到最小單位, 會將多個緩存對象緩存到一個緩存行
- 如果緩存對象不會被頻繁修改, 那么沒有必要直接字節(jié)

2. synchronized的實現(xiàn)原理與應(yīng)用

JDK1.6對synchronized關(guān)鍵字進行了各種優(yōu)化, 為了減少獲取和釋放鎖帶來的性能消耗, 引入了偏向鎖和輕量級鎖, 以及鎖的存儲結(jié)構(gòu)和升級過程

Java中的每一個對象都可以作為鎖

  • 普通同步方法, 鎖是當(dāng)前實例對象(this)
  • 靜態(tài)同步方法, 鎖是當(dāng)前類的Class對象
  • 同步方法塊, 鎖是synchronized括號里設(shè)置的對象
2.1 java對象頭

synchronized用的對象鎖是放在Java對象頭里面的

  • 如果對象是數(shù)組類型, 那么虛擬機使用3字寬(Word)存儲對象頭
  • 如果對象是非數(shù)組類型, 那么使用2字寬存儲對象頭
  • 32位虛擬機中, 1字寬等于4字節(jié)(Byte), 即32bit
  • 64位虛擬機中, 1字寬等于8字節(jié)(Byte), 即64bit
長度 內(nèi)容 說明
32/64bit Mark Word 存儲對象的hashcode或者鎖信息
32/64bit Class Metadata Address 存儲對象類型的指針
32/64bit Array length 如果當(dāng)前對象是數(shù)組, 那么存儲數(shù)組長度
Mark Word與鎖之間的對應(yīng)關(guān)系

Java1.6為了減少獲得鎖和釋放鎖帶來的消耗, 引入偏向鎖和輕量級鎖, 鎖一共有4種狀態(tài), 級別從低到高為無鎖狀態(tài), 偏向鎖狀態(tài), 輕量級鎖狀態(tài)和重量級鎖狀態(tài)

鎖可以升級但不能降級, 也就是偏向鎖升級為輕量級鎖之后, 不能降級為偏向鎖

2.2 鎖升級與對比
2.2.1 偏向鎖

大多數(shù)情況下, 鎖不存在多線程競爭, 并且總是由同一個線程多次獲取, 為了讓線程獲得鎖獲得鎖的代價更小, 引入了偏向鎖

偏向鎖是最樂觀的一種情況: 只有一個線程請求同一把鎖

(1).偏向鎖的獲取

當(dāng)一個線程訪問同步塊并獲取鎖的時候, 會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID, 之后線程訪問同步代碼塊的時候, 不需要進行使用CAS自旋鎖, 只要驗證一下對象頭的Mark Word里是否存放指向當(dāng)前線程的偏向鎖

  • 如果存儲了指向當(dāng)前線程的偏向鎖, 表示線程已經(jīng)獲取鎖, 可以直接訪問同步代碼塊

  • 如果沒有存儲指向當(dāng)前線程的偏向鎖, 則判斷Mark Word中偏向鎖標識是否是1, 表示當(dāng)前是偏向鎖

    • 如果是1的話, 嘗試使用CAS設(shè)置Mark Word偏向鎖線程ID, 指向當(dāng)前線程
    • 如果不是1的話, 則嘗試使用CAS競爭鎖

(2).偏向鎖的撤銷

偏向鎖要等到競爭出現(xiàn)才會釋放鎖, 當(dāng)其他線程嘗試競爭偏向鎖時, 持有偏向鎖的線程才會在全局安全點(鎖安全點,沒有正在執(zhí)行的字節(jié)碼)釋放鎖

  • 先暫停擁有偏向鎖的線程
  • 檢查持有偏向鎖的線程是否活著
    • 不處于活動狀態(tài)的話, 則將對象頭設(shè)置為無鎖狀態(tài)
    • 線程還活著的話, 遍歷偏向?qū)ο蟮逆i記錄, 棧中鎖記錄和對象頭的Mark Word可能執(zhí)行三種操作
      • 偏向于其他線程
      • 恢復(fù)到無鎖狀態(tài)
      • 線程仍然在執(zhí)行同步代碼塊, 會升級為輕量級鎖
  • 最后喚醒暫停的線程
輕量級鎖

通過自旋CAS來競爭鎖, 競爭的線程不會阻塞, 如果一直競爭不到鎖, 會消耗CPU

(1).加鎖

線程在執(zhí)行同步代碼塊之前, JVM會在當(dāng)前線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間, 并將對象頭的Mark Word復(fù)制到鎖記錄中, 官方稱Displaced Mark Word, 然后線程使用CAS將對象頭的Mark Word替換為指向鎖記錄的指針

  • 如果成功, 將Mark Word的鎖標記位設(shè)置為00,表示鎖對象處于輕量級鎖狀態(tài), 線程獲取到鎖
  • 如果失敗, 表示其他線程競爭鎖, 當(dāng)前線程嘗試使用CAS自旋獲取鎖, 自旋達到一定次數(shù)還沒有獲取到鎖, 會升級為重量級鎖

(2).解鎖
使用CAS操作將Displaced Mark Word替換回對象頭

  • 如何設(shè)置成功, 表示沒有發(fā)生競爭
  • 如果設(shè)置失敗, 表示當(dāng)前所鎖存在競爭, 就會膨脹為重量級鎖
重量級鎖

使用操作系統(tǒng)的互斥鎖(Mutex Lock)實現(xiàn), 當(dāng)進入重量級鎖之后, 就不會再降級為輕量級鎖, 當(dāng)其他線程占用鎖之后, 當(dāng)前線程會進入堵塞狀態(tài)

每個對象都擁有自己的監(jiān)視器, 線程必須要先獲取到對象監(jiān)視器才能進入同步塊或者同步方法, 沒有獲取到監(jiān)視器的線程將會被堵塞在同步代碼的入口處, 進入blocked狀態(tài)

對象監(jiān)視器依賴操作系統(tǒng)底層的mutex lock(互斥鎖)實現(xiàn)

3. 原子操作的實現(xiàn)原理

原子操作是不可被中斷的一個或者一系列操作

3.1 術(shù)語定義
術(shù)語名稱 解釋
緩存行 Cache line 緩存的最小操作單位
比較并交換 Compare and Swap CAS操作需要輸入兩個值, 一個舊值和一個新值, 操作之前比較舊值有沒有發(fā)生變化, 如果沒有發(fā)生變化, 才會交換成新值
CPU流水線 CPU pipeline 在CPU中, 由5到6個不同功能的電路單元組成一條指令處理流水線, 然后將一條X86指令分成5到6步后再由這些單元分別執(zhí)行, 這樣就能實現(xiàn)在一個CPU時鐘周期完成一條指令, 提高CPU運算速度
內(nèi)存順序沖突 Memory order violation 內(nèi)存順序沖突一般是由假共享引起的, 假共享是指多個CPU同時修改同一個緩存行的不同部分, 導(dǎo)致其中一個CPU操作無效, 當(dāng)出現(xiàn)內(nèi)存順序沖突時, CPU必須清空流水線
3.2 處理器如何實現(xiàn)原子操作

如果過個處理器同時對共享變量進行改寫操作, 就會導(dǎo)致共享變量的值和期望的值不一致, 因為多個處理器同時從各自的緩存中讀取變量, 分別操作, 然后分別寫入系統(tǒng)內(nèi)存中

處理器提供了總線鎖定和緩存鎖定兩種機制來保證復(fù)雜的內(nèi)存操作的原子性

3.2.1 總線鎖

CPU提供一個LOCK#信號, 當(dāng)一個處理器在總線上輸出此信號時, 其他處理器的請求將會被阻塞, 該處理器可以獨占共享內(nèi)存

總線鎖把CPU和內(nèi)存的通信鎖定住了, 鎖定期間, 其他處理器不能操作內(nèi)存的數(shù)據(jù), 所以總線鎖開銷比較大

3.2.2 緩存鎖

只需要保證某個內(nèi)存地址的操作是原子性即可, 頻繁使用的內(nèi)存會緩存在CPU高速緩存中, 那么原子操作就可以直接在CPU內(nèi)部緩存中進行, 使用緩存一致性來保證操作的原子性, 一般使用MESI控制協(xié)議, 保證緩存的一致性

有兩種情況CPU不使用緩存鎖定, 而使用總線鎖定:

  1. 操作的數(shù)據(jù)不能被緩存在CPU內(nèi)部或者操作的數(shù)據(jù)跨多個緩存行
  2. 有些CPU不支持緩存鎖定
3.3 java如何實現(xiàn)原子操作

java通過鎖和CAS方式來實現(xiàn)原子操作

3.3.1 CAS(Compare and Swap)

對一個內(nèi)存地址進行操作, CAS操作需要輸入兩個值, 一個舊值和一個新值, 操作之前比較舊值有沒有發(fā)生變化, 如果沒有發(fā)生變化, 才會交換成新值

Java中的Atomic原子類, AQS的底層實現(xiàn)都使用了CAS實現(xiàn)

CAS實現(xiàn)原子操作的三大問題:

  • ABA問題
    • CAS需要在操作值的時候, 檢查值有沒有發(fā)生變化, 如果沒有發(fā)生變化才更新, 但如果一個值原來是A, 變成了B, 又變成了A, 那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化
    • 解決思路是使用版本號, 在對象前面追加版本號, 每次版本號加1, JDK中使用AtomicStampedReference類解決ABA問題
    public class AtomicStampedReference<V> {
        
        /**
         * 舊值和舊版本號都相同, 才會把對象更新為新值和新版本號
         *
         * @param expectedReference 舊值
         * @param newReference 新值
         * @param expectedStamp 舊版本號
         * @param newStamp 新版本號
         * @return {@code true} 如果成功返回true
         */
        public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }
    
    }
    
  • 循環(huán)時間長開銷大, 自旋CAS如果長時間不成功, 那么會給CPU帶來非常大的開銷
  • 只能保證一個共享變量的原子操作
    • JDK提供了AtomicReference類保證引用對象之間的原子性, 可以把多個變量放在一個對象里進行CAS操作
3.3.2 鎖

鎖機制保證了只有獲得鎖的線程才能操作鎖定的內(nèi)存區(qū)域, JVM內(nèi)存實現(xiàn)了多種鎖機制, 有偏向鎖, 輕量級鎖和互斥鎖(重量解鎖)

除了偏向鎖, JVM實現(xiàn)鎖的方式都使用了CAS,

  • 當(dāng)有一個線程想進入同步塊的時候使用CAS的方式獲取鎖
  • 當(dāng)線程想退出同步塊的時候使用CAS方式釋放鎖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容