Java并發(fā)機(jī)制的底層原理

Java程序執(zhí)行:Java代碼→Java字節(jié)碼→字節(jié)碼被類(lèi)加載器加載到JVM里,JVM執(zhí)行字節(jié)碼→轉(zhuǎn)化為匯編指令在CPU上執(zhí)行。

Java中使用的并發(fā)機(jī)制依賴(lài)于JVM的實(shí)現(xiàn)和CPU的指令,本章我們來(lái)探索下Java并發(fā)機(jī)制的底層原理。


volatile的實(shí)現(xiàn)原理與應(yīng)用

在多線(xiàn)程的并發(fā)編程中synchronized和volatile都扮演著重要的角色,volatile是輕量級(jí)的synchronized,它在處理器開(kāi)發(fā)中保證了共享變量的“可見(jiàn)性”(可見(jiàn)性:當(dāng)一個(gè)線(xiàn)程修改一個(gè)共享變量時(shí),另外一個(gè)線(xiàn)程能讀到這個(gè)修改的值。)。

如果volatile變量修飾符使用恰當(dāng)?shù)脑?huà),它比synchronized的使用和執(zhí)行成本更低,因?yàn)樗?strong>不會(huì)引起上下文的切換和調(diào)度。

Java語(yǔ)言規(guī)范第3版中對(duì)volatile的定義如下:Java編程語(yǔ)言允許線(xiàn)程訪(fǎng)問(wèn)共享變量,為了確保共享變量能被準(zhǔn)確和一致的更新,線(xiàn)程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量。

如果一個(gè)字段被聲明稱(chēng)volatile,Java線(xiàn)程內(nèi)存模型確保所線(xiàn)程成看到這個(gè)變量的值是一致的。

volatile如何來(lái)保證可見(jiàn)性呢?

Java代碼:instance = new Singleton() //instance是volatile變量
轉(zhuǎn)為匯編代碼:0x01a3de1d: movb $0 ? 0, 0 ? 1104800(%esi);0x01a3de24: lock addl $0 ? 0,(%esp);
有volatile變量修飾的共享變量進(jìn)行寫(xiě)操作的時(shí)候會(huì)多處第二行匯編代碼,關(guān)鍵在與Lock指令,Lock前綴的指令在多核處理器下會(huì)引起兩件事情:

  1. 將當(dāng)前處理器緩存行的數(shù)據(jù)寫(xiě)回到系統(tǒng)內(nèi)存。
  2. 這個(gè)寫(xiě)回內(nèi)存的操作會(huì)使其它CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效。

詳細(xì)說(shuō)明:為了提高處理速度,處理器不直接和內(nèi)存進(jìn)行通信,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存后再進(jìn)行操作,但操作完不知道何時(shí)會(huì)寫(xiě)到內(nèi)存。如果對(duì)聲明了volatile的變量進(jìn)行寫(xiě)操作,JVM就會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫(xiě)回到系統(tǒng)內(nèi)存。但是,就算寫(xiě)回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作還是會(huì)有問(wèn)題。所以,在多處理器下,為了保證各個(gè)處理器的緩存是一致的,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議,每個(gè)處理器通過(guò)嗅探在總線(xiàn)上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存里。


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

在多線(xiàn)程并發(fā)編程中synchronized一直是元老級(jí)角色,被稱(chēng)為重量級(jí)鎖。但是,隨著Java SE 1.6對(duì)其進(jìn)行各種優(yōu)化,使得它在有些時(shí)候不是那么重。

利用synchronized實(shí)現(xiàn)同步的基礎(chǔ):Java的每一個(gè)對(duì)象都可以作為鎖。

  • 對(duì)于普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象。
  • 對(duì)于靜態(tài)同步方法,鎖是當(dāng)前類(lèi)的Class對(duì)象。
  • 對(duì)于同步方法塊,鎖是synchronized括號(hào)里配置的對(duì)象。

Java對(duì)象頭(暫時(shí)不細(xì)說(shuō)此概念)

synchronized用的鎖是存在Java對(duì)象頭里的。

鎖的升級(jí)與對(duì)比

Java SE 1.6為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”,在Java SE 1.6中,鎖一共有四種狀態(tài),級(jí)別從低到高一次:無(wú)鎖狀態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)

這幾個(gè)鎖會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí),鎖可以升級(jí)但不可以降級(jí)。這樣做的目的是為了提高獲得鎖和釋放鎖的效率。

優(yōu)點(diǎn) 缺點(diǎn) 適用場(chǎng)景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法相比僅存在納秒級(jí)的差距 如果線(xiàn)程間存在鎖競(jìng)爭(zhēng),會(huì)帶來(lái)額外的鎖撤銷(xiāo)的消耗 適用于只有一個(gè)線(xiàn)程訪(fǎng)問(wèn)同步快場(chǎng)景
輕量級(jí)鎖 競(jìng)爭(zhēng)的線(xiàn)程不會(huì)阻塞,提高程序的響應(yīng)速度 如果始終得不到鎖競(jìng)爭(zhēng)的線(xiàn)程,使用自旋會(huì)消耗CPU 追求響應(yīng)時(shí)間,同步塊執(zhí)行速度非???/td>
重量級(jí)鎖 線(xiàn)程競(jìng)爭(zhēng)不使用自旋,不會(huì)消耗CPU 線(xiàn)程阻塞,響應(yīng)時(shí)間緩慢 追求吞吐量,同步塊執(zhí)行速度較長(zhǎng)

原子操作的實(shí)現(xiàn)原理(大致聊一聊)

原子操作:不可被中斷的一個(gè)或一系列操作。

處理器如何實(shí)現(xiàn)原子操作:

  1. 使用總線(xiàn)鎖保證原子性。
  2. 使用緩存鎖定保證原子性。

但是有兩種特殊情況下處理器不會(huì)使用緩存鎖定:

  1. 當(dāng)操作的數(shù)據(jù)不能被緩存在處理器內(nèi)部,或操作的數(shù)據(jù)跨多個(gè)緩存行時(shí),則處理器會(huì)調(diào)用總線(xiàn)鎖定。
  2. 有些處理器不支持緩存鎖定。

Java如何實(shí)現(xiàn)原子操作:

使用循環(huán)CAS的方式實(shí)現(xiàn)原子操作。

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

  1. ABA問(wèn)題。
  2. 循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大。
  3. 只能保證一個(gè)共享變量的原子操作。

結(jié)語(yǔ):本篇Tomato主要了解下,volatile、synchronized、原子操作的原理,其中原子操作部分說(shuō)明的不是很詳細(xì),因?yàn)門(mén)omato自己還沒(méi)有研究明白,之后會(huì)進(jìn)行更新的。


最新更新:

(三)Java并發(fā)編程基礎(chǔ)—線(xiàn)程簡(jiǎn)介


并發(fā)編程專(zhuān)題目錄

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

(二)Java并發(fā)機(jī)制的底層原理

(三)Java并發(fā)編程基礎(chǔ)—線(xiàn)程簡(jiǎn)介


最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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