并發(fā)編程基礎(chǔ)二
Java 內(nèi)存模型
計(jì)算機(jī)硬件模型:

由于線程工作空間緩存的存在,主存數(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)志的變化而變化,它的變化如下表所示:

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ú)占、公平與非公平、可重入、自旋。