<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文看懂 Java 鎖機制,寫得太好了吧!

          共 10809字,需瀏覽 22分鐘

           ·

          2021-07-05 14:04

          背景知識

          指令流水線

          CPU的基本工作是執(zhí)行存儲的指令序列,即程序。程序的執(zhí)行過程實際上是不斷地取出指令、分析指令、執(zhí)行指令的過程。

          幾乎所有的馮?諾伊曼型計算機的CPU,其工作都可以分為5個階段:取指令、指令譯碼、執(zhí)行指令、訪存取數(shù)和結(jié)果寫回。

          現(xiàn)代處理器的體系結(jié)構(gòu)中,采用了流水線的處理方式對指令進行處理。指令包含了很多階段,對其進行拆解,每個階段由專門的硬件電路、寄存器來處 理,就可以實現(xiàn)流水線處理。實現(xiàn)更高的CPU吞吐量,但是由于流水線處理本身的額外開銷,可能會增加延遲。

          cpu多級緩存

          在計算機系統(tǒng)中,CPU高速緩存(CPU Cache,簡稱緩存)是用于減少處理器訪問內(nèi)存所需平均時間的部件。在金字塔式存儲體系中它位于自頂向下的第二層,僅次于CPU寄存器。其容量遠小于內(nèi)存,但速度卻可以接近處理器的頻率。

          當處理器發(fā)出內(nèi)存訪問請求時,會先查看緩存內(nèi)是否有請求數(shù)據(jù)。如果存在(命中),則不經(jīng)訪問內(nèi)存直接返回該數(shù)據(jù);如果不存在(失效),則要先把內(nèi)存中的相應數(shù)據(jù)載入緩存,再將其返回處理器。

          緩存之所以有效,主要是因為程序運行時對內(nèi)存的訪問呈現(xiàn)局部性(Locality)特征。這種局部性既包括空間局部性(Spatial Locality),也包括時間局部性(Temporal Locality)。有效利用這種局部性,緩存可以達到極高的命中率。

          問題引入

          原子性

          原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。

          示例方法:{i++ (i為實例變量)}

          這樣一個簡單語句主要由三個操作組成:

          • 讀取變量i的值
          • 進行加一操作
          • 將新的值賦值給變量i

          如果對實例變量i的操作不做額外的控制,那么多個線程同時調(diào)用,就會出現(xiàn)覆蓋現(xiàn)象,丟失部分更新。

          另外,如果再考慮上工作內(nèi)存和主存之間的交互,可細分為以下幾個操作:

          • read 從主存讀取到工作內(nèi)存 (非必須)
          • load 賦值給工作內(nèi)存的變量副本(非必須)
          • use 工作內(nèi)存變量的值傳給執(zhí)行引擎
          • 執(zhí)行引擎執(zhí)行加一操作
          • assign 把從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量
          • store 把工作內(nèi)存中的一個變量的值傳遞給主內(nèi)存(非必須)
          • write 把工作內(nèi)存中變量的值寫到主內(nèi)存中的變量(非必須)

          可見性

          可見性:是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值

          存在可見性問題的根本原因是由于緩存的存在,線程持有的是共享變量的副本,無法感知其他線程對于共享變量的更改,導致讀取的值不是最新的。

          while (flag) {//語句1
             doSomething();//語句2
          }
          flag = false;//語句3

          線程1判斷flag標記,滿足條件則執(zhí)行語句2;線程2flag標記置為false,但由于可見性問題,線程1無法感知,就會一直循環(huán)處理語句2。

          順序性

          順序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行

          由于編譯重排序和指令重排序的存在,是的程序真正執(zhí)行的順序不一定是跟代碼的順序一致,這種情況在多線程情況下會出現(xiàn)問題。

          if (inited == false) { 
             context = loadContext();   //語句1
             inited = true;             //語句2
          }
          doSomethingwithconfig(context); //語句3

          由于語句1和語句2沒有依賴性,語句1和語句2可能 并行執(zhí)行 或者 語句2先于語句1執(zhí)行,如果這段代碼兩個線程同時執(zhí)行,線程1執(zhí)行了語句2,而語句1還沒有執(zhí)行完,這個時候線程2判斷inited為true,則執(zhí)行語句3,但由于context沒有初始化完成,則會導致出現(xiàn)未知的異常。

          JMM內(nèi)存模型

          Java虛擬機規(guī)范定義了Java內(nèi)存模型(Java Memory Model,JMM)來屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓Java程序在各種平臺下都能達到一致的內(nèi)存訪問效果(C/C++等則直接使用物理機和OS的內(nèi)存模型,使得程序須針對特定平臺編寫),它在多線程的情況下尤其重要。

          內(nèi)存劃分

          JMM的主要目標是定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié)。這里的變量是指共享變量,存在競爭問題的變量,如實例字段、靜態(tài)字段、數(shù)組對象元素等,不包括線程私有的局部變量、方法參數(shù)等,因為私有變量不存在競爭問題。可以認為JMM包括內(nèi)存劃分、變量訪問操作與規(guī)則兩部分。

          分為主內(nèi)存和工作內(nèi)存,每個線程都有自己的工作內(nèi)存,它們共享主內(nèi)存。

          • 主內(nèi)存(Main Memory)存儲所有共享變量的值。
          • 工作內(nèi)存(Working Memory)存儲該線程使用到的共享變量在主內(nèi)存的的值的副本拷貝。

          線程對共享變量的所有讀寫操作都在自己的工作內(nèi)存中進行,不能直接讀寫主內(nèi)存中的變量。

          不同線程間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞必須通過主內(nèi)存完成。

          這種劃分與Java內(nèi)存區(qū)域中堆、棧、方法區(qū)等的劃分是不同層次的劃分,兩者基本沒有關(guān)系。硬要聯(lián)系的話,大致上主內(nèi)存對應Java堆中對象的實例數(shù)據(jù)部分、工作內(nèi)存對應棧的部分區(qū)域;從更低層次上說,主內(nèi)存對應物理硬件內(nèi)存、工作內(nèi)存對應寄存器和高速緩存。

          內(nèi)存間交互規(guī)則

          關(guān)于主內(nèi)存與工作內(nèi)存之間的交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存,如何從工作內(nèi)存同步到主內(nèi)存中的實現(xiàn)細節(jié)。Java內(nèi)存模型定義了8種原子操作來完成

          • lock: 將一個變量標識為被一個線程獨占狀態(tài)
          • unclock: 將一個變量從獨占狀態(tài)釋放出來,釋放后的變量才可以被其他線程鎖定
          • read: 將一個變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存中,以便隨后的load操作
          • load: 把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量的副本中
          • use: 把工作內(nèi)存中的一個變量的值傳給執(zhí)行引擎,每當虛擬機遇到一個使用到變量的指令時都會使用該指令
          • assign: 把一個從執(zhí)行引擎接收到的值賦給工作內(nèi)存中的變量,每當虛擬機遇到一個給變量賦值的指令時,都要使用該操作
          • store: 把工作內(nèi)存中的一個變量的值傳遞給主內(nèi)存,以便隨后的write操作
          • write: 把store操作從工作內(nèi)存中得到的變量的值寫到主內(nèi)存中的變量

          定義原子操作的使用規(guī)則

          1. 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步會主內(nèi)存中
          2. 一個新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個未被初始化(load或者assign)的變量。即就是對一個變量實施use和store操作之前,必須先自行assign和load操作。
          3. 一個變量在同一時刻只允許一條線程對其進行l(wèi)ock操作,但lock操作可以被同一線程重復執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會被解鎖。lock和unlock必須成對出現(xiàn)。
          4. 如果對一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量之前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
          5. 如果一個變量事先沒有被lock操作鎖定,則不允許對它執(zhí)行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。
          6. 對一個變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)

          從上面可以看出,把變量從主內(nèi)存復制到工作內(nèi)存需要順序執(zhí)行read、load,從工作內(nèi)存同步回主內(nèi)存則需要順序執(zhí)行store、write。總結(jié):

          • read、load、use必須成對順序出現(xiàn),但不要求連續(xù)出現(xiàn)。assign、store、write同之;
          • 變量誕生和初始化:變量只能從主內(nèi)存“誕生”,且須先初始化后才能使用,即在use/store前須先load/assign;
          • lock一個變量后會清空工作內(nèi)存中該變量的值,使用前須先初始化;unlock前須將變量同步回主內(nèi)存;
          • 一個變量同一時刻只能被一線程lock,lock幾次就須unlock幾次;未被lock的變量不允許被執(zhí)行unlock,一個線程不能去unlock其他線程lock的變量。

          long和double型變量的特殊規(guī)則

          Java內(nèi)存模型要求前述8個操作具有原子性,但對于64位的數(shù)據(jù)類型long和double,在模型中特別定義了一條寬松的規(guī)定:允許虛擬機將沒有被volatile修飾的64位數(shù)據(jù)的讀寫操作劃分為兩次32位的操作來進行。即未被volatile修飾時線程對其的讀取read不是原子操作,可能只讀到“半個變量”值。雖然如此,商用虛擬機幾乎都把64位數(shù)據(jù)的讀寫實現(xiàn)為原子操作,因此我們可以忽略這個問題。

          先行發(fā)生原則

          Java內(nèi)存模型具備一些先天的“有序性”,即不需要通過任何同步手段(volatile、synchronized等)就能夠得到保證的有序性,這個通常也稱為happens-before原則。

          如果兩個操作的執(zhí)行次序不符合先行原則且無法從happens-before原則推導出來,那么它們就不能保證它們的有序性,虛擬機可以隨意地對它們進行重排序。

          1. 程序次序規(guī)則(Program Order Rule):一個線程內(nèi),邏輯上書寫在前面的操作先行發(fā)生于書寫在后面的操作。
          2. 鎖定規(guī)則(Monitor Lock Rule):一個unLock操作先行發(fā)生于后面對同一個鎖的lock操作。“后面”指時間上的先后順序。
          3. volatile變量規(guī)則(Volatile Variable Rule):對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作。“后面”指時間上的先后順序。
          4. 傳遞規(guī)則(Transitivity):如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C。
          5. 線程啟動規(guī)則(Thread Start Rule):Thread對象的start()方法先行發(fā)生于此線程的每個一個動作。
          6. 線程中斷規(guī)則(Thread Interruption Rule):對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生(通過Thread.interrupted()檢測)。
          7. 線程終止規(guī)則(Thread Termination Rule):線程中所有的操作都先行發(fā)生于線程的終止檢測,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行。
          8. 對象終結(jié)規(guī)則(Finaizer Rule):一個對象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于他的finalize()方法的開始。

          問題解決

          原子性

          • 由JMM直接保證的原子性變量操作包括read、load、use、assign、store、write;
          • 基本數(shù)據(jù)類型的讀寫(工作內(nèi)存)是原子性的

          由JMM的lock、unlock可實現(xiàn)更大范圍的原子性保證,但是這是JVM需要實現(xiàn)支持的功能,對于開發(fā)者則是有由synchronized關(guān)鍵字 或者 Lock讀寫鎖 來保證原子性。

          可見性

          volatile 變量值被一個線程修改后會立即同步回主內(nèi)存、變量值被其他線程讀取前立即從主內(nèi)存刷新值到工作內(nèi)存。即read、load、use三者連續(xù)順序執(zhí)行,assign、store、write連續(xù)順序執(zhí)行。

          synchronized/Lock 由lock和unlock的使用規(guī)則保證

          • “對一個變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)”。
          • "如果對一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量之前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值"

          final 修飾的字段在構(gòu)造器中一旦初始化完成,且構(gòu)造器沒有把“this”的引用傳遞出去,則其他線程可立即看到final字段的值。

          順序性

          volatile 禁止指令重排序

          synchronized/Lock “一個變量在同一個時刻只允許一條線程對其執(zhí)行l(wèi)ock操作”

          開發(fā)篇

          volatile

          被volatile修飾的變量能保證器順序性和可見性

          順序性

          • 對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作。“后面”指時間上的先后順序

          可見性

          • 當寫一個 volatile 變量時,JMM 會把該線程對應的工作內(nèi)存中的共享變量刷新到主內(nèi)存。
          • 當讀一個 volatile 變量時,JMM 會把該線程對應的工作內(nèi)存置為無效,線程接下來將從主內(nèi)存中讀取共享變量。

          volatile相比于synchronized/Lock是非常輕量級,但是使用場景是有限制的:

          • 對變量的寫入操作不依賴于其當前值,即僅僅是讀取和單純的寫入,比如操作完成、中斷或者狀態(tài)之類的標志
          • 禁止對volatile變量操作指令的重排序

          實現(xiàn)原理

          volatile底層是通過cpu提供的內(nèi)存屏障指令來實現(xiàn)的。硬件層的內(nèi)存屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障。

          內(nèi)存屏障有兩個作用:

          • 阻止屏障兩側(cè)的指令重排序
          • 強制把寫緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫回主內(nèi)存,讓緩存中相應的數(shù)據(jù)失效

          final

          對于final域的內(nèi)存語義,編譯器和處理器要遵守兩個重排序規(guī)則(內(nèi)部實現(xiàn)也是使用內(nèi)存屏障):

          • 寫final域的重排序規(guī)則:在構(gòu)造函數(shù)內(nèi)對一個final域的寫入,與隨后把這個被構(gòu)造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
          • 讀final域的重排序規(guī)則:初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序。
          public class FinalExample {
                 int i;//普通域
                 final int j;//final域
                 static FinalExample obj;
                 
                 public FinalExample () {
                        i = 1;//寫普通域。對普通域的寫操作【可能會】被重排序到構(gòu)造函數(shù)之外 
                        j = 2;//寫final域。對final域的寫操作【不會】被重排序到構(gòu)造函數(shù)之外
                 }
                 
                 // 寫線程A執(zhí)行
                 public static void writer () {&emsp;   
                        obj = new FinalExample ();
                 }
                 
                 // 讀線程B執(zhí)行
                 public static void reader () {&emsp;   
                        FinalExample object = obj;//讀對象引用
                        int a = object.i;//讀普通域。可能會看到結(jié)果為0(由于i=1可能被重排序到構(gòu)造函數(shù)外,此時y還沒有被初始化)
                        int b = object.j;//讀final域。保證能夠看到結(jié)果為2
                 }
          }

          初次讀對象引用與初次讀該對象包含的final域,這兩個操作之間存在間接依賴關(guān)系。由于編譯器遵守間接依賴關(guān)系,因此編譯器不會重排序這兩個操作。大多數(shù)處理器也會遵守間接依賴,也不會重排序這兩個操作。但有少數(shù)處理器允許對存在間接依賴關(guān)系的操作做重排序(比如alpha處理器),這個規(guī)則就是專門用來針對這種處理器的。

          對于final域是引用類型,寫final域的重排序規(guī)則對編譯器和處理器增加了如下約束:

          • 在構(gòu)造函數(shù)內(nèi)對一個final引用的對象的成員域的寫入,與隨后在構(gòu)造函數(shù)外把這個被構(gòu)造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。

          synchronized

          synchronized用于修飾普通方法、修飾靜態(tài)方法、修飾代碼塊

          • 確保代碼的同步執(zhí)行(即不同線程間的互斥)(原子性)
          • 確保對共享變量的修改能夠及時可見(可見性)
          • 有效解決指令重排問題(順序性)

          實現(xiàn)原理

          使用對象的監(jiān)視器(Monitor,也有叫管程的)進行控制

          • 進入/加鎖時執(zhí)行字節(jié)碼指令MonitorEnter
          • 退出/解鎖時執(zhí)行字節(jié)碼指令MonitorExit
          • 當執(zhí)行代碼有異常退出方法/代碼段時,會自動解鎖

          使用哪個對象的監(jiān)視器:

          • 修飾對象方法時,使用當前對象的監(jiān)視器
          • 修飾靜態(tài)方法時,使用類類型(Class 的對象)監(jiān)視器
          • 修飾代碼塊時,使用括號中的對象的監(jiān)視器
          • 必須為 Object 類或其子類的對象

          MonitorEnter(加鎖)

          • 每個對象都有一個關(guān)聯(lián)的監(jiān)視器。
          • 監(jiān)視器被鎖住,當且僅當它有屬主(Owner)時。
          • 線程執(zhí)行MonitorEnter就是為了成為Monitor的屬主。
          • 如果 Monitor 對象的記錄數(shù)(Entry Count,擁有它的線程的重入次數(shù))為 0, 將其置為 1,線程將自己置為 Monitor 對象的屬主。
          • 如果Monitor的屬主為當前線程,就會重入監(jiān)視器,將其記錄數(shù)增一。
          • 如果Monitor的屬主為其它線程,當前線程會阻塞,直到記錄數(shù)為0,才會 去競爭屬主權(quán)。

          MonitorExit(解鎖):

          • 執(zhí)行MonitorExit的線程一定是這個對象所關(guān)聯(lián)的監(jiān)視器的屬主。
          • 線程將Monitor對象的記錄數(shù)減一。
          • 如果Monitor對象的記錄數(shù)為0,線程就會執(zhí)行退出動作,不再是屬主。
          • 此時其它阻塞的線程就被允許競爭屬主。

          對于 MonitorEnter、MonitorExit 來說,有兩個基本參數(shù):

          • 線程
          • 關(guān)聯(lián)監(jiān)視器的對象

          關(guān)鍵結(jié)構(gòu)

          在 JVM 中,對象在內(nèi)存中的布局分為三塊區(qū)域:對象頭、實例數(shù)據(jù)、對齊填充。如下:

          實例變量

          • 存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息
          • 如果是數(shù)組的實例變量,還包括數(shù)組的長度
          • 這部分內(nèi)存按4字節(jié)對齊

          填充數(shù)據(jù)

          • 由于虛擬機要求對象起始地址必須是8字節(jié)的整數(shù)倍
          • 填充數(shù)據(jù)僅僅是為了字節(jié)對齊
          • 保障下一個對象的起始地址為 8 的整數(shù)倍
          • 長度可能為0

          對象頭(Object Header)

          • 對象頭由 Mark Word 、Class Metadata Address(類元數(shù)據(jù)地址) 和 數(shù)組長度(對象為數(shù)組時)組成
          • 在 32 位和 64 位的虛擬機中,Mark Word 分別占用 32 字節(jié)和 64 字節(jié),因此稱其為 word

          Mark Word 存儲的并非對象的 實際業(yè)務數(shù)據(jù)(如對象的字段值),屬于 額外存儲成本。為了節(jié)約存儲空間,Mark Word 被設計為一個 非固定的數(shù)據(jù)結(jié)構(gòu),以便在盡量小的空間中存儲盡量多的數(shù)據(jù),它會根據(jù)對象的狀態(tài),變換自己的數(shù)據(jù)結(jié)構(gòu),從而復用自己的存儲空間。

          鎖的狀態(tài)共有 4 種:無鎖、偏向鎖、輕量級鎖、重量級鎖。隨著競爭的增加,鎖的使用情況如下:

          無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖

          其中偏向鎖和輕量級鎖是從 JDK 6 時引入的,在 JDK 6 中默認開啟。鎖的升級(鎖膨脹,inflate)是單向的,只能從低到高(從左到右)。不會出現(xiàn) 鎖的降級。

          偏向鎖

          當鎖對象第一次被線程獲取的時候,虛擬機將會把對象頭中的標志位設為“01” (可偏向),即偏向模式。同時使用CAS操作把獲取到這個鎖的線程的ID記錄在對象的Mark Word之中,如果CAS操作成功,持有偏向鎖的線程以后每次進入這個鎖相關(guān)的同步塊時,虛擬機都可以不再進行任何同步操作。

          當有另外一個線程去嘗試獲取這個鎖時,偏向模式就宣告結(jié)束。根據(jù)鎖對象目前是否處于被鎖定的狀態(tài),撤銷偏向(Revoke Bias)后恢復到未鎖定(標志位為“01”,不可偏向)或 輕量級鎖定(標志位為“00”)的狀態(tài),后續(xù)的同步操作就進入輕量級鎖的流程。

          輕量級鎖

          進入到輕量級鎖說明不止一個線程嘗試獲取鎖,這個階段會通過自適應自旋CAS方式獲取鎖。如果獲取失敗,則進行鎖膨脹,進入重量級鎖流程,線程阻塞。

          重量級鎖

          重量級鎖是通過系統(tǒng)的線程互斥鎖來實現(xiàn)的,代價最昂貴

          ContentionList,CXQ,存放最近競爭鎖的線程

          • LIFO,單向鏈表
          • 很多線程都可以把請求鎖的線程放入隊列中
          • 但只有一個線程能將線程出隊

          EntryLis,表示勝者組

          • 雙向鏈表
          • 只有擁有鎖的線程才可以訪問或變更 EntryLis
          • 只有擁有鎖的線程在釋放鎖時,并且在 EntryList 為空、ContentionList 不為 空的情況下,才能將ContentionList 中的線程全部出隊,放入到EntryList 中

          WaitSet,存放處于等待狀態(tài)的線程

          • 將進行 wait() 調(diào)用的線程放入WaitSet
          • 當進行 notify()、notifyAll()調(diào)用時,會將線程放入到ContentionList或EntryList 隊列中

          注意:

          • 對一個線程而言,在任何時候最多只處于三個集合中的一個
          • 處于這三個集合中的線程,均為 BLOCKED 狀態(tài),底層使用互斥量來進行阻塞

          當一個線程成功獲取到鎖時 對象監(jiān)視器的 owner 字段從 NULL 變?yōu)榉强眨赶虼司€程 必須將自己從ContentionList或EntryList中出隊

          競爭型的鎖傳遞機制 線程釋放鎖時,不保證后繼線程一定可以獲得到鎖,而是后繼線程去競爭鎖

          OnDeck,表示準備就緒的線程,保證任何時候都只有一個線程來直接競爭 鎖

          • 在獲取鎖時,如果發(fā)生競爭,則使用自旋鎖來爭用,如果自旋后仍得不 到,再放入上述隊列中。
          • 自旋可以減少ContentionList和EntryList上出隊入隊的操作,也就是減少了內(nèi)部 維護的這些鎖的爭用。

          OS 互斥鎖

          重量級鎖是通過操作系統(tǒng)的線程互斥鎖來實現(xiàn)的,在 Linux 下,鎖所用的技術(shù)是 pthead_mutex_lock / pthead_mutex_unlock,即線程間的互斥鎖。

          線程互斥鎖是基于 futex(Fast Userspace Mutex)機制實現(xiàn)的。常規(guī)的操作系統(tǒng)的同步機制(如 IPC 等),調(diào)用時都需要陷入到內(nèi)核中執(zhí)行,即使沒有競爭也要執(zhí)行一次陷入操作(int 0x80,trap)。而 futex 則是內(nèi)核態(tài)和用戶態(tài)的混合,無競爭時,獲取鎖和釋放鎖都不需要陷入內(nèi)核。

          初始分配

          首先在內(nèi)存分配 futex 共享變量,對線程而言,內(nèi)存是共享的,直接分配(malloc)即可,為整數(shù)類型,初始值為1。

          獲取鎖

          使用CAS對 futex 變量減1,觀察其結(jié)果:

          • 如果由1變?yōu)?,表示無競爭,繼續(xù)執(zhí)行
          • 如果小于 0,表示有競爭,調(diào)用 futex(..., FUTEX_WAIT, ...) 使當前線程休眠

          釋放鎖

          使用CAS給futex變量加1

          • 如果futex變量由0變?yōu)?,表示無競爭,繼續(xù)執(zhí)行
          • 如果 futex 變量變化前為負值,表示有競爭,調(diào)用 futex(..., FUTEX_WAKE, ...) 喚醒一個或多個等待線程

          Lock

          另寫一篇專門講解AQS的鎖機制的文章

          作者:VectorJin
          來源:https://juejin.cn/post/6844904036026548237

          最近給大家找了  Vue進階


          資源,怎么領(lǐng)取?


          掃二維碼,加我微信,回復:Vue進階

           注意,不要亂回復 

          沒錯,不是機器人
          記得一定要等待,等待才有好東西
          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  521av在线观看 | 中文字幕 亚洲 日本 欧美 | 午夜久久福利 | 欧美黄色日韩 | 久久久视频在线观看 |