1. 程式人生 > >Synchronize實現原理(很難)

Synchronize實現原理(很難)

在C程式程式碼中我們可以利用作業系統提供的互斥鎖來實現同步塊的互斥訪問及執行緒的阻塞及喚醒等工作。然而在Java中除了提供Lock API外還在語法層面上提供了synchronized關鍵字來實現互斥同步原語。那麼到底在JVM內部是怎麼實現synchronized關鍵子的呢?

一、synchronized的位元組碼錶示: 

      在java語言中存在兩種內建的synchronized語法:1、synchronized語句;2、synchronized方法。對於synchronized語句當Java原始碼被javac編譯成bytecode的時候,會在同步塊的入口位置和退出位置分別插入

monitorentermonitorexit位元組碼指令。而synchronized方法則會被翻譯成普通的方法呼叫和返回指令如:invokevirtual、areturn指令,在VM位元組碼層面並沒有任何特別的指令來實現被synchronized修飾的方法,而是在Class檔案的方法表中將該方法的access_flags欄位中的synchronized標誌位置1,表示該方法是同步方法並使用呼叫該方法的物件或該方法所屬的Class在JVM的內部物件表示Class做為鎖物件。

二、JVM中鎖的優化:

      簡單來說在JVM中monitorenter和monitorexit位元組碼依賴於底層的作業系統的Mutex Lock來實現的,但是由於使用Mutex Lock需要將當前執行緒掛起並從使用者態切換到核心態來執行,這種切換的代價是非常昂貴的;然而在現實中的大部分情況下,同步方法是執行在單執行緒環境(無鎖競爭環境)如果每次都呼叫Mutex Lock那麼將嚴重的影響程式的效能。不過在jdk1.6中對鎖的實現引入了大量的優化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應性自旋(Adaptive Spinning)等技術來減少鎖操作的開銷。

鎖粗化(Lock Coarsening): 也就是減少不必要的緊連在一起的unlock,lock操作,將多個連續的鎖擴充套件成一個範圍更大的鎖。

鎖消除(Lock Elimination): 通過執行時JIT編譯器的逃逸分析來消除一些沒有在當前同步塊以外被其他執行緒共享的資料的鎖保護,通過逃逸分析也可以線上程本地Stack上進行物件空間的分配(同時還可以減少Heap上的垃圾收集開銷)。

輕量級鎖(Lightweight Locking): 這種鎖實現的背後基於這樣一種假設,即在真實的情況下我們程式中的大部分同步程式碼一般都處於無鎖競爭狀態(即單執行緒執行環境),在無鎖競爭的情況下完全可以避免呼叫作業系統層面的重量級互斥鎖,取而代之的是在monitorenter和monitorexit中只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放。當存在鎖競爭的情況下,執行CAS指令失敗的執行緒將呼叫作業系統互斥鎖進入到阻塞狀態,當鎖被釋放的時候被喚醒(具體處理步驟下面詳細討論)。

偏向鎖(Biased Locking): 是為了在無鎖競爭的情況下避免在鎖獲取過程中執行不必要的CAS原子指令,因為CAS原子指令雖然相對於重量級鎖來說開銷比較小但還是存在非常可觀的本地延遲(可參考這篇 文章 )。

適應性自旋(Adaptive Spinning): 當執行緒在獲取輕量級鎖的過程中執行CAS操作失敗時,在進入與monitor相關聯的作業系統重量級鎖(mutex semaphore)前會進入忙等待(Spinning)然後再次嘗試,當嘗試一定的次數後如果仍然沒有成功則呼叫與該monitor關聯的semaphore(即互斥鎖)進入到阻塞狀態。

三、物件頭(Object Header):


在JVM中建立物件時會在物件前面加上兩個字大小的物件頭,在32位機器上一個字為32bit,根據不同的狀態位Mark World中存放不同的內容,如上圖所示在輕量級鎖中,Mark Word被分成兩部分,剛開始時LockWord為被設定為HashCode、最低三位表示LockWord所處的狀態,初始狀態為001表示無鎖狀態。Klass ptr指向Class位元組碼在虛擬機器內部的物件表示的地址。Fields表示連續的物件例項欄位。

四、Monitor Record:

 Monitor Record是執行緒私有的資料結構,每一個執行緒都有一個可用monitor record列表,同時還有一個全域性的可用列表;那麼這些monitor record有什麼用呢?每一個被鎖住的物件都會和一個monitor record關聯(物件頭中的LockWord指向monitor record的起始地址,由於這個地址是8byte對齊的所以LockWord的最低三位可以用來作為狀態位),同時monitor record中有一個Owner欄位存放擁有該鎖的執行緒的唯一標識,表示該鎖被這個執行緒佔用。如下圖所示為Monitor Record的內部結構: 


Owner:初始時為NULL表示當前沒有任何執行緒擁有該monitor record,當執行緒成功擁有該鎖後儲存執行緒唯一標識,當鎖被釋放時又設定為NULL;

EntryQ:關聯一個系統互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的執行緒。

RcThis:表示blocked或waiting在該monitor record上的所有執行緒的個數。

Nest:用來實現重入鎖的計數。

HashCode:儲存從物件頭拷貝過來的HashCode值(可能還包含GC age)。

Candidate:用來避免不必要的阻塞或等待執行緒喚醒,因為每一次只有一個執行緒能夠成功擁有鎖,如果每次前一個釋放鎖的執行緒喚醒所有正在阻塞或等待的執行緒,會引起不必要的上下文切換(從阻塞到就緒然後因為競爭鎖失敗又被阻塞)從而導致效能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的執行緒1表示要喚醒一個繼任執行緒來競爭鎖。

五、輕量級鎖具體實現:

 一個執行緒能夠通過兩種方式鎖住一個物件:1、通過膨脹一個處於無鎖狀態(狀態位001)的物件獲得該物件的鎖;2、物件已經處於膨脹狀態(狀態位00)但LockWord指向的monitor record的Owner欄位為NULL,則可以直接通過CAS原子指令嘗試將Owner設定為自己的標識來獲得鎖。

獲取鎖(monitorenter)的大概過程如下:

(1)當物件處於無鎖狀態時(RecordWord值為HashCode,狀態位為001),執行緒首先從自己的可用moniter record列表中取得一個空閒的moniter record,初始Nest和Owner值分別被預先設定為1和該執行緒自己的標識,一旦monitor record準備好然後我們通過CAS原子指令安裝該monitor record的起始地址到物件頭的LockWord欄位來膨脹(原文為inflate,我覺得之所以叫inflate主要是由於當物件被膨脹後擴充套件了物件的大小;為了空間效率,將monitor record結構從物件頭中抽出去,當需要的時候才將該結構attach到物件上,但是和這篇 Paper 有點互相矛盾,兩種實現方式稍微有點不同)該物件,如果存在其他執行緒競爭鎖的情況而呼叫CAS失敗,則只需要簡單的回到monitorenter重新開始獲取鎖的過程即可。 

(2)物件已經被膨脹同時Owner中儲存的執行緒標識為獲取鎖的執行緒自己,這就是重入(reentrant)鎖的情況,只需要簡單的將Nest加1即可。不需要任何原子操作,效率非常高。

(3)物件已膨脹但Owner的值為NULL,當一個鎖上存在阻塞或等待的執行緒同時鎖的前一個擁有者剛釋放鎖時會出現這種狀態,此時多個執行緒通過CAS原子指令在多執行緒競爭狀態下試圖將Owner設定為自己的標識來獲得鎖,競爭失敗的執行緒在則會進入到第四種情況(4)的執行路徑。

(4)物件處於膨脹狀態同時Owner不為NULL(被鎖住),在呼叫作業系統的重量級的互斥鎖之前先自旋一定的次數,當達到一定的次數時如果仍然沒有成功獲得鎖,則開始準備進入阻塞狀態,首先將rfThis的值原子性的加1,由於在加1的過程中可能會被其他執行緒破壞Object和monitor record之間的關聯,所以在原子性加1後需要再進行一次比較以確保LockWord的值沒有被改變,當發現被改變後則要重新進行monitorenter過程。同時再一次觀察Owner是否為NULL,如果是則呼叫CAS參與競爭鎖,鎖競爭失敗則進入到阻塞狀態。

釋放鎖(monitorexit)的大概過程如下:

(1)首先檢查該物件是否處於膨脹狀態並且該執行緒是這個鎖的擁有者,如果發現不對則丟擲異常;

(2)檢查Nest欄位是否大於1,如果大於1則簡單的將Nest減1並繼續擁有鎖,如果等於1,則進入到(3);

(3)檢查rfThis是否大於0,設定Owner為NULL然後喚醒一個正在阻塞或等待的執行緒再一次試圖獲取鎖,如果等於0則進入到(4)

(4)縮小(deflate)一個物件,通過將物件的LockWord置換回原來的HashCode值來解除和monitor record之間的關聯來釋放鎖,同時將monitor record放回到執行緒是有的可用monitor record列表。

相關推薦

Synchronize實現原理

在C程式程式碼中我們可以利用作業系統提供的互斥鎖來實現同步塊的互斥訪問及執行緒的阻塞及喚醒等工作。然而在Java中除了提供Lock API外還在語法層面上提供了synchronized關鍵字來實現互斥同步原語。那麼到底在JVM內部是怎麼實現synchronized關鍵子的

注意力機制的基本思想和實現原理詳細(第二篇)

    上述內容就是經典的Soft Attention模型的基本思想,那麼怎麼理解Attention模型的物理含義呢?一般在自然語言處理應用裡會把Attention模型看作是輸出Target句子中某個單詞和輸入Source句子每個單詞的對齊模型,這是非常有道理的。    目標句子生成的每個單詞對應輸入句子單詞

Promise實現原理附原始碼

本篇文章主要在於探究 Promise 的實現原理,帶領大家一步一步實現一個 Promise , 不對其用法做說明,如果讀者還對Promise的用法不瞭解,可以檢視阮一峰老師的ES6 Promise教程。 接下來,帶你一步一步實現一個 Promise 1. Promise 基本結

執行緒池實現原理Executor框架,java提供常用的幾種執行緒池、死鎖產生條件和避免

 為什麼使用執行緒池 伺服器應用程式中經常出現的情況是:單個任務處理的時間很短而請求的數目卻是巨大的。如果每個請求對應一個執行緒(thread-per-request)方法的不足之一是:為每個請求建立一個新執行緒的開銷很大;為每個請求建立新執行緒的伺服器在建立和銷燬執行緒上

揭祕 HashMap 實現原理Java 8

HashMap 作為一種容器型別,無論你是否瞭解過其內部的實現原理,它的大名已經頻頻出現在各種網際網路面試中了。從基本的使用角度來說,它很簡單,但從其內部的實現來看(尤其是 Java 8 的改進以來),它又並非想象中那麼容易。如果你一定要問了解其內部實現與否對於寫程式究竟有多大影響,我不能給出一個確切的答案。

HashMap底層實現原理學習筆記

  看了一上午,原始碼看的頭疼,果斷放棄,放個連結吧 https://www.cnblogs.com/chengxiao/p/6059914.html jdk1.8原始碼解析:https://blog.csdn.net/xp2234/article/details/801

epoll底層實現原理通俗易懂

2013-10-27更新:由於此文陸陸續續收到贊同,而且其中有些地方並不完全正確,特在本文最後予以訂正 我不瞭解樓主的層次,我必須從很多基礎的概念開始構建這個答案,並且可能引申到很多別的問題。 首先我們來定義流的概念,一個流可以是檔案,socket,pipe等等可以進行I/O操作的核心物件。 不管是檔案,還是

FBKVOController實現原理簡單描述

在看這篇文章之前,建議自己寫一個小的FBKVOController Example,如果懶得寫可以在Github上clone我寫的一個非常簡單的example。這樣能建立一個大體的瞭解。 對程式碼中的細節部分沒有做介紹,例如鎖機制,Set,Map這些,只要知道

抽象類呼叫自己的抽象方法,實現來自實現常用

直接上程式碼 public abstract class Parent { public abstract void dosomething(); public void say(){ dosomething(); Sys

沉浸式的簡單實現效果簡單

在Activity中實現這個方法,就可以實現沉浸式,但是必須在5.0以上的系統中實現 public void onWindowFocusChanged(boolean hasFocus) {//沉浸式 super.onWindowFocusChanged(hasFocus

Android 相機開發 Camera-附帶掃碼遮罩介面實現原理自動聚焦

相機遮罩實現原理,FrameLayout中如果控制元件一樣大,同一時間只能見到最上面的。 1.自定義ViewFindView,相機遮罩介面 public class ViewFinderView extends View { //相機遮罩框外面的線,陰影區域,滾

NAT路由器“打洞”技術,即P2P通訊實現原理非常詳細

什麼是打洞,為什麼要打洞 由於Internet的快速發展 IPV4地址不夠用,不能每個主機分到一個公網IP 所以使用NAT地址轉換。 下面是我在網上找到的一副圖 一般來說都是由私網內主機(例如上圖中“電腦A-01”)主動發起連線,資料包經過NAT地址轉換後送給公網上的伺服器(例如上圖中的“Server”)

九種跨域方式實現原理完整版

cdn ogl 解決方案 跳板 target 圖片 nginx服務 rem 根據 摘要: 徹底掌握跨域。 前言 前後端數據交互經常會碰到請求跨域,什麽是跨域,以及有哪幾種跨域方式,這是本文要探討的內容。 本文完整的源代碼請猛戳GitHub博客,紙上得來終覺淺,建議動手敲敲代

框架源碼系列八:Spring源碼學習之Spring核心工作原理重要

ted pos avi Edito 重要 explicit mon alt 構造函數 目錄:一、搞清楚ApplicationContext實例化Bean的過程二、搞清楚這個過程中涉及的核心類三、搞清楚IOC容器提供的擴展點有哪些,學會擴展四、學會IOC容器這裏使用的設計模式

LinkedList實現原理JDK1.8

LinkedList實現原理(JDK1.8) LinkedList底層採用雙向連結串列,如果對連結串列這種結構比較熟悉的話,那LinkedList的實現原理看明白就相當容易。 連結串列通過“指標”將一組零散的記憶體塊串聯起來使用,每一個元素(節點)通過指標指向它的下一個元素,最後一個節點的下一個指向為nu

Zookeeper之Zookeeper底層客戶端架構實現原理轉載

一次 描述 綁定 機制 一個 ini fin 源碼 receive Zookeeper的Client直接與用戶打交道,是我們使用Zookeeper的interface。了解ZK Client的結構和工作原理有利於我們合理的使用ZK,並能在使用中更早的發現問題。本文將在研究源

Spring技術內幕:Spring AOP的實現原理

dede ide configure ida mini == src min dem 生成SingleTon代理對象在getSingleTonInstance方法中完畢,這種方法時ProxyFactoryBean生成AopProxy對象的入口。代理對象會

JS 實現無縫滾動動畫原理初學者入

padding absolute hidden 高度 pos sof add align meta   這段時間在教培訓班的學生使用原生javascript實現無縫滾動的動畫案例,做了這個原理演示的動畫,分享給自學JS的朋友!博主希望對你們有幫助! 在講解之前先看一下de

單點登錄SSO的實現原理

客戶 解決方案 bus 應用集成 eight 冗余 請求 效率 att 單點登錄SSO(Single Sign On)說得簡單點就是在一個多系統共存的環境下,用戶在一處登錄後,就不用在其他系統中登錄,也就是用戶的一次登錄能得到其他所有系統的信任。單點登錄在大型網站裏使用得非

Java樂觀鎖的實現原理案例

extends 默認 tomat 讀取數據 pac creat fifo for ava 簡要說明: 表設計時,需要往表裏加一個version字段。每次查詢時,查出帶有version的數據記錄,更新數據時,判斷數據庫裏對應id的記錄的version是否和查出的version