欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

Java鎖機(jī)制的原理和應(yīng)用

這篇文章主要介紹“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é)果寫回。 Java鎖機(jī)制的原理和應(yīng)用 現(xiàn)代處理器的體系結(jié)構(gòu)中,采用了流水線的處理方式對(duì)指令進(jìn)行處理。指令包含了很多階段,對(duì)其進(jìn)行拆解,每個(gè)階段由專門的硬件電路、寄存器來處 理,就可以實(shí)現(xiàn)流水線處理。實(shí)現(xiàn)更高的CPU吞吐量,但是由于流水線處理本身的額外開銷,可能會(huì)增加延遲。

cpu多級(jí)緩存Java鎖機(jī)制的原理和應(yīng)用

在計(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ī)則兩部分。 Java鎖機(jī)制的原理和應(yīng)用 分為主內(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種原子操作來完成: Java鎖機(jī)制的原理和應(yīng)用

  • 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ǔ)空間。 Java鎖機(jī)制的原理和應(yīng)用 鎖的狀態(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à)最昂貴 Java鎖機(jī)制的原理和應(yīng)用 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)

成都app開發(fā)公司