1. 程式人生 > >JVM內部細節之一:synchronized關鍵字及實現細節(輕量級鎖Lightweight Locking)

JVM內部細節之一:synchronized關鍵字及實現細節(輕量級鎖Lightweight Locking)

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

一、synchronized的位元組碼錶示:

      在java語言中存在兩種內建的synchronized語法:1、synchronized語句;2、synchronized方法。對於synchronized語句當Java原始碼被javac編譯成bytecode的時候,會在同步塊的入口位置和退出位置分別插入monitorenter和monitorexit位元組碼指令。而synchronized方法則會被翻譯成普通的方法呼叫和返回指令如:invokevirtual、areturn指令,在VM位元組碼層面並沒有任何特別的指令來實現被synchronized修飾的方法,而是在Class檔案的方法表中將該方法的access_flags欄位中的synchronized標誌位置1,表示該方法是同步方法並使用呼叫該方法的物件或該方法所屬的Class在JVM的內部物件表示Klass做為鎖物件。

二、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列表。

六、參考資料:

注:有理解錯誤之處歡迎指出,謝謝!

相關推薦

JVM內部細節之一synchronized關鍵字實現細節輕量級Lightweight Locking

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

Java多線程synchronized關鍵字和Lock

final sleep java多線 大型 pre 有一個 但是 logs 讀寫文件 一、synchronized   synchronized關鍵字可以用於聲明方法,也可以用來聲明代碼塊,下面分別看一下具體的場景(摘抄自《大型網站系統與Java中間件實踐》) 案例一:

團隊作業之一團隊介紹選題背景與意義

benefit 設計 旅行 sdk class clip 操作系統 開發環境 方便 團隊作業之一:團隊介紹及選題背景與意義 團隊介紹及組員情況: 1、組名:大吉大利,今晚加班 2、成員: 班級 姓名 身份 計算1511 張林 組員 計

Synchronized關鍵字物件

程式碼示例: 修飾程式碼塊:  執行結果: 兩個例項分別呼叫test1()方法,程式碼交叉執行,說明Synchronized修飾程式碼塊時,兩個執行緒獲取的鎖不一樣,不同的呼叫物件各自獲取自己的this物件鎖,this代表當前的例項物件。 修飾方法:

多執行緒2synchronized關鍵字

  多執行緒操作相同資源的時候,會出現執行緒安全的問題,導致結果與預期的不一致。   如下例子,設計四個執行緒,其中兩個對執行緒對變數加1操作,兩個執行緒對變數減1操作。理想狀態是,執行緒順序執行,相同次數的加減操作,最後變數的值不變。 1.執行緒不安全的操作 p

JAVA併發程式設計synchronized關鍵字深入解析

生活 天氣賊好的一個禮拜二的吃完晚飯的晚上。 他們去聽課了。 不想寫程式碼。 我在這看點東西吧~ 閒談 對於synchronized的記憶是最早對同步的概念。那時候聊到同步,就會說到StringBuilder和StringBuffer,裡面 的方法都是一樣的,但是StringBu

【搞定Java併發程式設計】第5篇synchronized關鍵字

多執行緒上篇:https://blog.csdn.net/pcwl1206/article/details/84837530 多執行緒下篇:https://blog.csdn.net/pcwl1206/article/details/84843170 本文轉發自:https://blog.c

SpringSecurity學習筆記之一SpringSecurity概述Github專案克隆

Spring Security是一種基於Spring AOP和Servlet規範中的Filter實現的安全框架。它能夠在Web請求級別和方法呼叫級別處理身份認證和授權。 Spring Security從兩個角度來解決安全性問題。 它使用Servlet規範中的Filter保護

Javasynchronized關鍵字

原帖收藏於IT老兵部落格。 前言 這裡翻譯一篇對Java的synchronized關鍵字的講解文章,這個關鍵字用於解決Java世界裡面競爭條件時的訪問衝突問題。 正文 Java synchronized塊將方法或程式碼塊標記為已同步。 Java synchronized塊可用

JVM筆記》之一Java記憶體區域與記憶體溢位異常

Java與C++之間有一堵由記憶體動態分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裡面的人卻想出來。 按照《Java虛擬機器規範(第2版)》的規定,Java虛擬機器所管理的記憶體將包括以下幾個執行時資料區域,來個圖更加直觀點,如下圖所示:   解釋下各個部分 程式計數器: Program Coun

DNS伺服器之一DNS簡介BIND安裝與基本配置

1、什麼是DNS? 域名系統(英文:Domain Name System,DNS)是因特網的一項服務,它作為將域名和IP地址相互對映的一個分散式資料庫,能夠使人更方便的訪問網際網路。DNS 使用TCP和UDP埠53。當前,對於每一級域名長度的限制是63個字元,域名總長度則不

Ext4.2.1學習歷程之一環境搭建Hello ExtJS4.2

原文出處    http://blog.itpub.net/28562677/viewspace-1066765/ 1、從官網下載ExtJS4.2資源包,解壓開有原始碼、API文件、演示程式; 2、官網地址:http://www.sencha.com/products/e

多執行緒synchronized關鍵字解析

原理 synchronized是JVM層面的鎖,是一種重量級的鎖。synchronized可以同步方法和程式碼塊。 public class Synchronized { public static void main(String[] args) { /

Weex開發體驗之一環境搭建調測

搭建開發環境 參考文件 本機開發環境 windows7 64bit 搭建步驟 下載node node包括javascript的執行環境和npm; 下載地址:https://nodejs.org/zh-cn/download/ 安裝好後,檢測

【Java學習筆記之一】 java關鍵字作用

父類 extends 硬件 引號 保留 var goto xxx owa 目錄 Java關鍵字及其作用 一、 總覽: 訪問控制 private protected public 類,方法和變量修飾符 abstract cl

用程式碼說話synchronized關鍵字和多執行緒訪問同步方法的7種情況

synchronized關鍵字在多執行緒併發程式設計中一直是元老級角色的存在,是學習併發程式設計中必須面對的坎,也是走向Java高階開發的必經之路。 一、synchronized性質 synchronized是Java提供的內建鎖機制,有如下兩種特性: 互斥性:即在同一時間最多隻有一個執行緒能持有這種鎖。當

java線程總結--synchronized關鍵字,原理以及相關的

public 關鍵字 多線程 java 文章 在多線程編程中,synchronized關鍵字非常常見,當我們需要進行“同步”操作時,我們很多時候需要該該關鍵字對代碼塊或者方法進行鎖定。被synchronized鎖定的代碼塊,只能同時有一條線程訪問該代碼塊。上面是很多人的認識,當然也是我之前

java執行緒總結--synchronized關鍵字,原理以及相關的

在多執行緒程式設計中,synchronized關鍵字非常常見,當我們需要進行“同步”操作時,我們很多時候需要該該關鍵字對程式碼塊或者方法進行鎖定。被synchronized鎖定的程式碼塊,只能同時有一條執行緒訪問該程式碼塊。 上面是很多人的認識,當然也是我之前對synchronized關鍵字的淺

UVM暫存器篇之一暫存器模型概覽

本文轉自:http://www.eetop.cn/blog/html/28/1561828-6266218.html 對於硬體有了解的讀者,都知道暫存器是模組之間互相交談的視窗。一方面可以通過讀出暫存器的狀態,獲取硬體當前的狀況,另外一方面也可以通過配置暫存器,使得暫存器工作在一定的模式下。而在

JVM虛擬機選項Xms Xmx PermSize MaxPermSize區別

生產 申請 java ane 獨立 use 我們 也會 交換 java雖然是自動回收內存,但是應用程序,尤其服務器程序最好根據業務情況指明內存分配限制。否則可能導致應用程序宕掉。舉例說明含義:-Xms128m表示JVM Heap(堆內存)最小尺寸128MB,初始分配-Xmx