Java多線程編程二 并發(fā)編程基礎(chǔ)(下)

并發(fā)編程基礎(chǔ)二

Java 內(nèi)存模型

計(jì)算機(jī)硬件模型:

jmm.jpg

由于線程工作空間緩存的存在,主存數(shù)據(jù)在多線程環(huán)境下會(huì)出現(xiàn)讀寫(xiě)不一致情況。

監(jiān)視器鎖 synchronized

鎖對(duì)象分為兩種:

實(shí)例對(duì)象和 Class 對(duì)象。

不同鎖對(duì)象之間的代碼執(zhí)行互不干擾,同一個(gè)類(lèi)中加鎖方法與不加鎖方法執(zhí)行互不干擾。

使用 synchronized 兩種方式:

修飾普通方法,鎖當(dāng)前實(shí)例對(duì)象。修飾靜態(tài)方法,鎖當(dāng)前類(lèi)的 Class 對(duì)象。

修飾代碼塊,鎖括號(hào)中的對(duì)象(實(shí)例對(duì)象或 Class 對(duì)象)。

synchronized特性

原子性

一系列操作要么全部執(zhí)行要不全部不執(zhí)行 -- 原子性。

被 synchronized 修飾的代碼在同一時(shí)間只能被一個(gè)線程訪問(wèn),在鎖未釋放之前,無(wú)法被其他線程訪問(wèn)到。因此,在 Java 中可以使用 synchronized 來(lái)保證方法和代碼塊內(nèi)的操作是原子性的。

可見(jiàn)性

對(duì)一個(gè)變量解鎖之前,必須先把此變量同步回主存中。這樣解鎖后,后續(xù)線程就可以訪問(wèn)到被修改后的值。

有序性

synchronized 本身是無(wú)法禁止指令重排和處理器優(yōu)化的。

as-if-serial 語(yǔ)義:不管怎么重排序(編譯器和處理器為了提高并行度),單線程程序的執(zhí)行結(jié)果都不能被改變。

編譯器和處理器無(wú)論如何優(yōu)化,都必須遵守 as-if-serial 語(yǔ)義。

synchronized 修飾的代碼,同一時(shí)間只能被同一線程執(zhí)行。所以,可以保證其有序性。

synchronized鎖原理

JVM 基于進(jìn)入和推出Monitor對(duì)象(每個(gè)線程私有一個(gè)可用monitor record列表)來(lái)實(shí)現(xiàn)方法和同步代碼塊,但兩者的實(shí)現(xiàn)細(xì)節(jié)不同。

同步代碼塊:使用monitorenter 和 monitorexit 指令實(shí)現(xiàn)。

synchronize修飾的方法:取代之的是 ACC_SYNCHRONIZED標(biāo)識(shí),該標(biāo)志指明了該方法是一個(gè)同步方法,從而執(zhí)行相應(yīng)的同步調(diào)用。

對(duì)于不同的對(duì)象頭它們的總結(jié)如下表:

長(zhǎng)度 內(nèi)容 說(shuō)明
32/64bit MarkWord 存儲(chǔ)對(duì)象的hashCode,GC分代和鎖信息
32/64bit Klass Point 存儲(chǔ)到類(lèi)元數(shù)據(jù)的指針
32/64bit Array Length 這個(gè)只針對(duì)數(shù)組對(duì)象而言,存儲(chǔ)數(shù)組的長(zhǎng)度

Java對(duì)象頭中的MarkWord里面的存儲(chǔ)對(duì)象如下表:

鎖狀態(tài) 25bit 4bit 1bit是否偏向鎖 2bit鎖標(biāo)志
無(wú)鎖狀態(tài) 對(duì)象的hashCode 對(duì)象分代年齡 0 01

在運(yùn)行期間,Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志的變化而變化,它的變化如下表所示:

concurrent22.png

synchronized鎖優(yōu)化(JDK6)

鎖優(yōu)化:無(wú)鎖->偏向鎖(Mark Word簡(jiǎn)單判斷是否為當(dāng)前線程再次請(qǐng)求獲取鎖)->

輕量級(jí)鎖(采用CAS自旋獲取鎖)->重量級(jí)鎖(會(huì)發(fā)生上下文切換,性能低)

鎖膨脹不可逆

解決內(nèi)存可見(jiàn)性 volatile

使用鎖太笨重,對(duì)于解決內(nèi)存可見(jiàn)性問(wèn)題,Java 提供了弱形式的同步-- volatile 。該關(guān)鍵字確保對(duì)一個(gè)變量的更新對(duì)其他線程是可見(jiàn)的,線程中數(shù)據(jù)寫(xiě)入與讀取是直接操作主內(nèi)存而不是使用線程的緩存(工作空間)。

volatile 不是原子性的,僅保證了數(shù)據(jù)的可見(jiàn)性。當(dāng)修改的新值依賴舊值時(shí),獲取 - 計(jì)算 -寫(xiě)入 ,三步操作不是原子的。

Java 指令的重排列

在執(zhí)行程序時(shí)為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排列。單線程下結(jié)果一致,多線程下存在問(wèn)題。

使用 volatile 修飾變量 可以避免重排列。

其他

了解 Java 的 CAS 操作 與 Unsafe 類(lèi) 比較并交換

了解 偽分享 概念--多個(gè)變量被存在同一個(gè)緩存行,多線程下讀取與修改不同的資源會(huì)不斷的刷新緩存,影響性能。(@sun.misc.Contended )

鎖的概述:樂(lè)觀與悲觀、共享與獨(dú)占公平與非公平、可重入自旋。

最后編輯于
?著作權(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)容