這篇文章主要介紹“Java鎖機(jī)制的原理和應(yīng)用”,在日常操作中,相信很多人在Java鎖機(jī)制的原理和應(yīng)用問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Java鎖機(jī)制的原理和應(yīng)用”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
我們提供的服務(wù)有:做網(wǎng)站、成都網(wǎng)站制作、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、洪澤ssl等。為近1000家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的洪澤網(wǎng)站制作公司
背景知識(shí)
指令流水線
CPU的基本工作是執(zhí)行存儲(chǔ)的指令序列,即程序。程序的執(zhí)行過程實(shí)際上是不斷地取出指令、分析指令、執(zhí)行指令的過程。
幾乎所有的馮?諾伊曼型計(jì)算機(jī)的CPU,其工作都可以分為5個(gè)階段:取指令、指令譯碼、執(zhí)行指令、訪存取數(shù)和結(jié)果寫回。
現(xiàn)代處理器的體系結(jié)構(gòu)中,采用了流水線的處理方式對(duì)指令進(jìn)行處理。指令包含了很多階段,對(duì)其進(jìn)行拆解,每個(gè)階段由專門的硬件電路、寄存器來處 理,就可以實(shí)現(xiàn)流水線處理。實(shí)現(xiàn)更高的CPU吞吐量,但是由于流水線處理本身的額外開銷,可能會(huì)增加延遲。
cpu多級(jí)緩存
在計(jì)算機(jī)系統(tǒng)中,CPU高速緩存(CPU Cache,簡(jiǎn)稱緩存)是用于減少處理器訪問內(nèi)存所需平均時(shí)間的部件。在金字塔式存儲(chǔ)體系中它位于自頂向下的第二層,僅次于CPU寄存器。其容量遠(yuǎn)小于內(nèi)存,但速度卻可以接近處理器的頻率。
當(dāng)處理器發(fā)出內(nèi)存訪問請(qǐng)求時(shí),會(huì)先查看緩存內(nèi)是否有請(qǐng)求數(shù)據(jù)。如果存在(命中),則不經(jīng)訪問內(nèi)存直接返回該數(shù)據(jù);如果不存在(失效),則要先把內(nèi)存中的相應(yīng)數(shù)據(jù)載入緩存,再將其返回處理器。
緩存之所以有效,主要是因?yàn)槌绦蜻\(yùn)行時(shí)對(duì)內(nèi)存的訪問呈現(xiàn)局部性(Locality)特征。這種局部性既包括空間局部性(Spatial Locality),也包括時(shí)間局部性(Temporal Locality)。有效利用這種局部性,緩存可以達(dá)到極高的命中率。
問題引入
原子性
原子性:即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷,要么就都不執(zhí)行。
示例方法:{i++ (i為實(shí)例變量)}
這樣一個(gè)簡(jiǎn)單語句主要由三個(gè)操作組成:
讀取變量i的值
進(jìn)行加一操作
將新的值賦值給變量i
如果對(duì)實(shí)例變量i的操作不做額外的控制,那么多個(gè)線程同時(shí)調(diào)用,就會(huì)出現(xiàn)覆蓋現(xiàn)象,丟失部分更新。
另外,如果再考慮上工作內(nèi)存和主存之間的交互,可細(xì)分為以下幾個(gè)操作:
read 從主存讀取到工作內(nèi)存 (非必須)
load 賦值給工作內(nèi)存的變量副本(非必須)
use 工作內(nèi)存變量的值傳給執(zhí)行引擎
執(zhí)行引擎執(zhí)行加一操作
assign 把從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量
store 把工作內(nèi)存中的一個(gè)變量的值傳遞給主內(nèi)存(非必須)
write 把工作內(nèi)存中變量的值寫到主內(nèi)存中的變量(非必須)
可見性
可見性:是指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值
存在可見性問題的根本原因是由于緩存的存在,線程持有的是共享變量的副本,無法感知其他線程對(duì)于共享變量的更改,導(dǎo)致讀取的值不是最新的。
while (flag) {//語句1
doSomething();//語句2
}
flag = false;//語句3線程1判斷flag標(biāo)記,滿足條件則執(zhí)行語句2;線程2flag標(biāo)記置為false,但由于可見性問題,線程1無法感知,就會(huì)一直循環(huán)處理語句2。
順序性
順序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行
由于編譯重排序和指令重排序的存在,是的程序真正執(zhí)行的順序不一定是跟代碼的順序一致,這種情況在多線程情況下會(huì)出現(xiàn)問題。
if (inited == false) {
context = loadContext(); //語句1
inited = true; //語句2
}
doSomethingwithconfig(context); //語句3由于語句1和語句2沒有依賴性,語句1和語句2可能 并行執(zhí)行 或者 語句2先于語句1執(zhí)行,如果這段代碼兩個(gè)線程同時(shí)執(zhí)行,線程1執(zhí)行了語句2,而語句1還沒有執(zhí)行完,這個(gè)時(shí)候線程2判斷inited為true,則執(zhí)行語句3,但由于context沒有初始化完成,則會(huì)導(dǎo)致出現(xiàn)未知的異常。
JMM內(nèi)存模型
Java虛擬機(jī)規(guī)范定義了Java內(nèi)存模型(Java Memory Model,JMM)來屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問效果(C/C++等則直接使用物理機(jī)和OS的內(nèi)存模型,使得程序須針對(duì)特定平臺(tái)編寫),它在多線程的情況下尤其重要。
內(nèi)存劃分
JMM的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。這里的變量是指共享變量,存在競(jìng)爭(zhēng)問題的變量,如實(shí)例字段、靜態(tài)字段、數(shù)組對(duì)象元素等,不包括線程私有的局部變量、方法參數(shù)等,因?yàn)樗接凶兞坎淮嬖诟?jìng)爭(zhēng)問題。可以認(rèn)為JMM包括內(nèi)存劃分、變量訪問操作與規(guī)則兩部分。
分為主內(nèi)存和工作內(nèi)存,每個(gè)線程都有自己的工作內(nèi)存,它們共享主內(nèi)存。
主內(nèi)存(Main Memory)存儲(chǔ)所有共享變量的值。
工作內(nèi)存(Working Memory)存儲(chǔ)該線程使用到的共享變量在主內(nèi)存的的值的副本拷貝。
線程對(duì)共享變量的所有讀寫操作都在自己的工作內(nèi)存中進(jìn)行,不能直接讀寫主內(nèi)存中的變量。
不同線程間也無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞必須通過主內(nèi)存完成。
這種劃分與Java內(nèi)存區(qū)域中堆、棧、方法區(qū)等的劃分是不同層次的劃分,兩者基本沒有關(guān)系。硬要聯(lián)系的話,大致上主內(nèi)存對(duì)應(yīng)Java堆中對(duì)象的實(shí)例數(shù)據(jù)部分、工作內(nèi)存對(duì)應(yīng)棧的部分區(qū)域;從更低層次上說,主內(nèi)存對(duì)應(yīng)物理硬件內(nèi)存、工作內(nèi)存對(duì)應(yīng)寄存器和高速緩存。
內(nèi)存間交互規(guī)則
關(guān)于主內(nèi)存與工作內(nèi)存之間的交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存,如何從工作內(nèi)存同步到主內(nèi)存中的實(shí)現(xiàn)細(xì)節(jié)。Java內(nèi)存模型定義了8種原子操作來完成: 
lock: 將一個(gè)變量標(biāo)識(shí)為被一個(gè)線程獨(dú)占狀態(tài)
unclock: 將一個(gè)變量從獨(dú)占狀態(tài)釋放出來,釋放后的變量才可以被其他線程鎖定
read: 將一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存中,以便隨后的load操作
load: 把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量的副本中
use: 把工作內(nèi)存中的一個(gè)變量的值傳給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)使用到變量的指令時(shí)都會(huì)使用該指令
assign: 把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存中的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的指令時(shí),都要使用該操作
store: 把工作內(nèi)存中的一個(gè)變量的值傳遞給主內(nèi)存,以便隨后的write操作
write: 把store操作從工作內(nèi)存中得到的變量的值寫到主內(nèi)存中的變量
定義原子操作的使用規(guī)則
1.不允許一個(gè)線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步會(huì)主內(nèi)存中
2.一個(gè)新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或者assign)的變量。即就是對(duì)一個(gè)變量實(shí)施use和store操作之前,必須先自行assign和load操作。
3.一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖。lock和unlock必須成對(duì)出現(xiàn)。
4.如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量之前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
5.如果一個(gè)變量事先沒有被lock操作鎖定,則不允許對(duì)它執(zhí)行unlock操作;也不允許去unlock一個(gè)被其他線程鎖定的變量。
6.對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)
從上面可以看出,把變量從主內(nèi)存復(fù)制到工作內(nèi)存需要順序執(zhí)行read、load,從工作內(nèi)存同步回主內(nèi)存則需要順序執(zhí)行store、write。總結(jié):
read、load、use必須成對(duì)順序出現(xiàn),但不要求連續(xù)出現(xiàn)。assign、store、write同之;
變量誕生和初始化:變量只能從主內(nèi)存“誕生”,且須先初始化后才能使用,即在use/store前須先load/assign;
lock一個(gè)變量后會(huì)清空工作內(nèi)存中該變量的值,使用前須先初始化;unlock前須將變量同步回主內(nèi)存;
一個(gè)變量同一時(shí)刻只能被一線程lock,lock幾次就須unlock幾次;未被lock的變量不允許被執(zhí)行unlock,一個(gè)線程不能去unlock其他線程lock的變量。
long和double型變量的特殊規(guī)則
Java內(nèi)存模型要求前述8個(gè)操作具有原子性,但對(duì)于64位的數(shù)據(jù)類型long和double,在模型中特別定義了一條寬松的規(guī)定:允許虛擬機(jī)將沒有被volatile修飾的64位數(shù)據(jù)的讀寫操作劃分為兩次32位的操作來進(jìn)行。即未被volatile修飾時(shí)線程對(duì)其的讀取read不是原子操作,可能只讀到“半個(gè)變量”值。雖然如此,商用虛擬機(jī)幾乎都把64位數(shù)據(jù)的讀寫實(shí)現(xiàn)為原子操作,因此我們可以忽略這個(gè)問題。
先行發(fā)生原則
Java內(nèi)存模型具備一些先天的“有序性”,即不需要通過任何同步手段(volatile、synchronized等)就能夠得到保證的有序性,這個(gè)通常也稱為happens-before原則。
如果兩個(gè)操作的執(zhí)行次序不符合先行原則且無法從happens-before原則推導(dǎo)出來,那么它們就不能保證它們的有序性,虛擬機(jī)可以隨意地對(duì)它們進(jìn)行重排序。
程序次序規(guī)則(Program Order Rule):一個(gè)線程內(nèi),邏輯上書寫在前面的操作先行發(fā)生于書寫在后面的操作。
鎖定規(guī)則(Monitor Lock Rule):一個(gè)unLock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。“后面”指時(shí)間上的先后順序。
volatile變量規(guī)則(Volatile Variable Rule):對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。“后面”指時(shí)間上的先后順序。
傳遞規(guī)則(Transitivity):如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C。
線程啟動(dòng)規(guī)則(Thread Start Rule):Thread對(duì)象的start()方法先行發(fā)生于此線程的每個(gè)一個(gè)動(dòng)作。
線程中斷規(guī)則(Thread Interruption Rule):對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生(通過Thread.interrupted()檢測(cè))。
線程終止規(guī)則(Thread Termination Rule):線程中所有的操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行。
對(duì)象終結(jié)規(guī)則(Finaizer Rule):一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于他的finalize()方法的開始。
問題解決
原子性
由JMM直接保證的原子性變量操作包括read、load、use、assign、store、write;
基本數(shù)據(jù)類型的讀寫(工作內(nèi)存)是原子性的
由JMM的lock、unlock可實(shí)現(xiàn)更大范圍的原子性保證,但是這是JVM需要實(shí)現(xiàn)支持的功能,對(duì)于開發(fā)者則是有由synchronized關(guān)鍵字 或者 Lock讀寫鎖 來保證原子性。
可見性
volatile 變量值被一個(gè)線程修改后會(huì)立即同步回主內(nèi)存、變量值被其他線程讀取前立即從主內(nèi)存刷新值到工作內(nèi)存。即read、load、use三者連續(xù)順序執(zhí)行,assign、store、write連續(xù)順序執(zhí)行。
synchronized/Lock 由lock和unlock的使用規(guī)則保證
“對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)”。
"如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量之前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值"
final 修飾的字段在構(gòu)造器中一旦初始化完成,且構(gòu)造器沒有把“this”的引用傳遞出去,則其他線程可立即看到final字段的值。
順序性
volatile 禁止指令重排序
synchronized/Lock “一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其執(zhí)行l(wèi)ock操作”
開發(fā)篇
volatile
被volatile修飾的變量能保證器順序性和可見性
順序性
對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。“后面”指時(shí)間上的先后順序
可見性
當(dāng)寫一個(gè) volatile 變量時(shí),JMM 會(huì)把該線程對(duì)應(yīng)的工作內(nèi)存中的共享變量刷新到主內(nèi)存。
當(dāng)讀一個(gè) volatile 變量時(shí),JMM 會(huì)把該線程對(duì)應(yīng)的工作內(nèi)存置為無效,線程接下來將從主內(nèi)存中讀取共享變量。
volatile相比于synchronized/Lock是非常輕量級(jí),但是使用場(chǎng)景是有限制的:
對(duì)變量的寫入操作不依賴于其當(dāng)前值,即僅僅是讀取和單純的寫入,比如操作完成、中斷或者狀態(tài)之類的標(biāo)志
禁止對(duì)volatile變量操作指令的重排序
實(shí)現(xiàn)原理
volatile底層是通過cpu提供的內(nèi)存屏障指令來實(shí)現(xiàn)的。硬件層的內(nèi)存屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障。
內(nèi)存屏障有兩個(gè)作用:
阻止屏障兩側(cè)的指令重排序
強(qiáng)制把寫緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫回主內(nèi)存,讓緩存中相應(yīng)的數(shù)據(jù)失效
final
對(duì)于final域的內(nèi)存語義,編譯器和處理器要遵守兩個(gè)重排序規(guī)則(內(nèi)部實(shí)現(xiàn)也是使用內(nèi)存屏障):
寫final域的重排序規(guī)則:在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final域的寫入,與隨后把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序。
讀final域的重排序規(guī)則:初次讀一個(gè)包含final域的對(duì)象的引用,與隨后初次讀這個(gè)final域,這兩個(gè)操作之間不能重排序。
public class FinalExample {
int i;//普通域
final int j;//final域
static FinalExample obj;
public FinalExample () {
i = 1;//寫普通域。對(duì)普通域的寫操作【可能會(huì)】被重排序到構(gòu)造函數(shù)之外
j = 2;//寫final域。對(duì)final域的寫操作【不會(huì)】被重排序到構(gòu)造函數(shù)之外
}
// 寫線程A執(zhí)行
public static void writer () { 
obj = new FinalExample ();
}
// 讀線程B執(zhí)行
public static void reader () { 
FinalExample object = obj;//讀對(duì)象引用
int a = object.i;//讀普通域。可能會(huì)看到結(jié)果為0(由于i=1可能被重排序到構(gòu)造函數(shù)外,此時(shí)y還沒有被初始化)
int b = object.j;//讀final域。保證能夠看到結(jié)果為2
}
}初次讀對(duì)象引用與初次讀該對(duì)象包含的final域,這兩個(gè)操作之間存在間接依賴關(guān)系。由于編譯器遵守間接依賴關(guān)系,因此編譯器不會(huì)重排序這兩個(gè)操作。大多數(shù)處理器也會(huì)遵守間接依賴,也不會(huì)重排序這兩個(gè)操作。但有少數(shù)處理器允許對(duì)存在間接依賴關(guān)系的操作做重排序(比如alpha處理器),這個(gè)規(guī)則就是專門用來針對(duì)這種處理器的。
對(duì)于final域是引用類型,寫final域的重排序規(guī)則對(duì)編譯器和處理器增加了如下約束:
在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final引用的對(duì)象的成員域的寫入,與隨后在構(gòu)造函數(shù)外把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序。
synchronized
synchronized用于修飾普通方法、修飾靜態(tài)方法、修飾代碼塊
確保代碼的同步執(zhí)行(即不同線程間的互斥)(原子性)
確保對(duì)共享變量的修改能夠及時(shí)可見(可見性)
有效解決指令重排問題(順序性)
實(shí)現(xiàn)原理
使用對(duì)象的監(jiān)視器(Monitor,也有叫管程的)進(jìn)行控制
進(jìn)入/加鎖時(shí)執(zhí)行字節(jié)碼指令MonitorEnter
退出/解鎖時(shí)執(zhí)行字節(jié)碼指令MonitorExit
當(dāng)執(zhí)行代碼有異常退出方法/代碼段時(shí),會(huì)自動(dòng)解鎖
使用哪個(gè)對(duì)象的監(jiān)視器:
修飾對(duì)象方法時(shí),使用當(dāng)前對(duì)象的監(jiān)視器
修飾靜態(tài)方法時(shí),使用類類型(Class 的對(duì)象)監(jiān)視器
修飾代碼塊時(shí),使用括號(hào)中的對(duì)象的監(jiān)視器
必須為 Object 類或其子類的對(duì)象
MonitorEnter(加鎖):
每個(gè)對(duì)象都有一個(gè)關(guān)聯(lián)的監(jiān)視器。
監(jiān)視器被鎖住,當(dāng)且僅當(dāng)它有屬主(Owner)時(shí)。
線程執(zhí)行MonitorEnter就是為了成為Monitor的屬主。
如果 Monitor 對(duì)象的記錄數(shù)(Entry Count,擁有它的線程的重入次數(shù))為 0, 將其置為 1,線程將自己置為 Monitor 對(duì)象的屬主。
如果Monitor的屬主為當(dāng)前線程,就會(huì)重入監(jiān)視器,將其記錄數(shù)增一。
如果Monitor的屬主為其它線程,當(dāng)前線程會(huì)阻塞,直到記錄數(shù)為0,才會(huì) 去競(jìng)爭(zhēng)屬主權(quán)。
MonitorExit(解鎖):
執(zhí)行MonitorExit的線程一定是這個(gè)對(duì)象所關(guān)聯(lián)的監(jiān)視器的屬主。
線程將Monitor對(duì)象的記錄數(shù)減一。
如果Monitor對(duì)象的記錄數(shù)為0,線程就會(huì)執(zhí)行退出動(dòng)作,不再是屬主。
此時(shí)其它阻塞的線程就被允許競(jìng)爭(zhēng)屬主。
對(duì)于 MonitorEnter、MonitorExit 來說,有兩個(gè)基本參數(shù):
線程
關(guān)聯(lián)監(jiān)視器的對(duì)象
關(guān)鍵結(jié)構(gòu)
在 JVM 中,對(duì)象在內(nèi)存中的布局分為三塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)齊填充。如下:
實(shí)例變量
存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息
如果是數(shù)組的實(shí)例變量,還包括數(shù)組的長(zhǎng)度
這部分內(nèi)存按4字節(jié)對(duì)齊
填充數(shù)據(jù)
由于虛擬機(jī)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍
填充數(shù)據(jù)僅僅是為了字節(jié)對(duì)齊
保障下一個(gè)對(duì)象的起始地址為 8 的整數(shù)倍
長(zhǎng)度可能為0
對(duì)象頭(Object Header)
對(duì)象頭由 Mark Word 、Class Metadata Address(類元數(shù)據(jù)地址) 和 數(shù)組長(zhǎng)度(對(duì)象為數(shù)組時(shí))組成
在 32 位和 64 位的虛擬機(jī)中,Mark Word 分別占用 32 字節(jié)和 64 字節(jié),因此稱其為 word
Mark Word 存儲(chǔ)的并非對(duì)象的 實(shí)際業(yè)務(wù)數(shù)據(jù)(如對(duì)象的字段值),屬于 額外存儲(chǔ)成本。為了節(jié)約存儲(chǔ)空間,Mark Word 被設(shè)計(jì)為一個(gè) 非固定的數(shù)據(jù)結(jié)構(gòu),以便在盡量小的空間中存儲(chǔ)盡量多的數(shù)據(jù),它會(huì)根據(jù)對(duì)象的狀態(tài),變換自己的數(shù)據(jù)結(jié)構(gòu),從而復(fù)用自己的存儲(chǔ)空間。
鎖的狀態(tài)共有 4 種:無鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。隨著競(jìng)爭(zhēng)的增加,鎖的使用情況如下:
無鎖 -> 偏向鎖 -> 輕量級(jí)鎖 -> 重量級(jí)鎖
其中偏向鎖和輕量級(jí)鎖是從 JDK 6 時(shí)引入的,在 JDK 6 中默認(rèn)開啟。鎖的升級(jí)(鎖膨脹,inflate)是單向的,只能從低到高(從左到右)。不會(huì)出現(xiàn) 鎖的降級(jí)。
偏向鎖
當(dāng)鎖對(duì)象第一次被線程獲取的時(shí)候,虛擬機(jī)將會(huì)把對(duì)象頭中的標(biāo)志位設(shè)為“01” (可偏向),即偏向模式。同時(shí)使用CAS操作把獲取到這個(gè)鎖的線程的ID記錄在對(duì)象的Mark Word之中,如果CAS操作成功,持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí),虛擬機(jī)都可以不再進(jìn)行任何同步操作。
當(dāng)有另外一個(gè)線程去嘗試獲取這個(gè)鎖時(shí),偏向模式就宣告結(jié)束。根據(jù)鎖對(duì)象目前是否處于被鎖定的狀態(tài),撤銷偏向(Revoke Bias)后恢復(fù)到未鎖定(標(biāo)志位為“01”,不可偏向)或 輕量級(jí)鎖定(標(biāo)志位為“00”)的狀態(tài),后續(xù)的同步操作就進(jìn)入輕量級(jí)鎖的流程。
輕量級(jí)鎖
進(jìn)入到輕量級(jí)鎖說明不止一個(gè)線程嘗試獲取鎖,這個(gè)階段會(huì)通過自適應(yīng)自旋CAS方式獲取鎖。如果獲取失敗,則進(jìn)行鎖膨脹,進(jìn)入重量級(jí)鎖流程,線程阻塞。
重量級(jí)鎖
重量級(jí)鎖是通過系統(tǒng)的線程互斥鎖來實(shí)現(xiàn)的,代價(jià)最昂貴
ContentionList,CXQ,存放最近競(jìng)爭(zhēng)鎖的線程
LIFO,單向鏈表
很多線程都可以把請(qǐng)求鎖的線程放入隊(duì)列中
但只有一個(gè)線程能將線程出隊(duì)
EntryLis,表示勝者組
雙向鏈表
只有擁有鎖的線程才可以訪問或變更 EntryLis
只有擁有鎖的線程在釋放鎖時(shí),并且在 EntryList 為空、ContentionList 不為 空的情況下,才能將ContentionList 中的線程全部出隊(duì),放入到EntryList 中
WaitSet,存放處于等待狀態(tài)的線程
將進(jìn)行 wait() 調(diào)用的線程放入WaitSet
當(dāng)進(jìn)行 notify()、notifyAll()調(diào)用時(shí),會(huì)將線程放入到ContentionList或EntryList 隊(duì)列中
注意:
對(duì)一個(gè)線程而言,在任何時(shí)候最多只處于三個(gè)集合中的一個(gè)
處于這三個(gè)集合中的線程,均為 BLOCKED 狀態(tài),底層使用互斥量來進(jìn)行阻塞
當(dāng)一個(gè)線程成功獲取到鎖時(shí) 對(duì)象監(jiān)視器的 owner 字段從 NULL 變?yōu)榉强眨赶虼司€程 必須將自己從ContentionList或EntryList中出隊(duì)
競(jìng)爭(zhēng)型的鎖傳遞機(jī)制 線程釋放鎖時(shí),不保證后繼線程一定可以獲得到鎖,而是后繼線程去競(jìng)爭(zhēng)鎖
OnDeck,表示準(zhǔn)備就緒的線程,保證任何時(shí)候都只有一個(gè)線程來直接競(jìng)爭(zhēng) 鎖
在獲取鎖時(shí),如果發(fā)生競(jìng)爭(zhēng),則使用自旋鎖來爭(zhēng)用,如果自旋后仍得不 到,再放入上述隊(duì)列中。
自旋可以減少ContentionList和EntryList上出隊(duì)入隊(duì)的操作,也就是減少了內(nèi)部 維護(hù)的這些鎖的爭(zhēng)用。
OS 互斥鎖
重量級(jí)鎖是通過操作系統(tǒng)的線程互斥鎖來實(shí)現(xiàn)的,在 Linux 下,鎖所用的技術(shù)是 pthead_mutex_lock / pthead_mutex_unlock,即線程間的互斥鎖。
線程互斥鎖是基于 futex(Fast Userspace Mutex)機(jī)制實(shí)現(xiàn)的。常規(guī)的操作系統(tǒng)的同步機(jī)制(如 IPC 等),調(diào)用時(shí)都需要陷入到內(nèi)核中執(zhí)行,即使沒有競(jìng)爭(zhēng)也要執(zhí)行一次陷入操作(int 0x80,trap)。而 futex 則是內(nèi)核態(tài)和用戶態(tài)的混合,無競(jìng)爭(zhēng)時(shí),獲取鎖和釋放鎖都不需要陷入內(nèi)核。
初始分配
首先在內(nèi)存分配 futex 共享變量,對(duì)線程而言,內(nèi)存是共享的,直接分配(malloc)即可,為整數(shù)類型,初始值為1。
獲取鎖
使用CAS對(duì) futex 變量減1,觀察其結(jié)果:
如果由1變?yōu)?,表示無競(jìng)爭(zhēng),繼續(xù)執(zhí)行
如果小于 0,表示有競(jìng)爭(zhēng),調(diào)用 futex(..., FUTEX_WAIT, ...) 使當(dāng)前線程休眠
釋放鎖
使用CAS給futex變量加1
如果futex變量由0變?yōu)?,表示無競(jìng)爭(zhēng),繼續(xù)執(zhí)行
如果 futex 變量變化前為負(fù)值,表示有競(jìng)爭(zhēng),調(diào)用 futex(..., FUTEX_WAKE, ...) 喚醒一個(gè)或多個(gè)等待線程
到此,關(guān)于“Java鎖機(jī)制的原理和應(yīng)用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
本文題目:Java鎖機(jī)制的原理和應(yīng)用
本文來源:http://www.chinadenli.net/article18/iieegp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、云服務(wù)器、虛擬主機(jī)、域名注冊(cè)、做網(wǎng)站、網(wǎng)站改版
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)