1. 程式人生 > >Java 中 synchronized 的實現原理及偏向鎖、輕量級鎖、自旋鎖、公平鎖簡介

Java 中 synchronized 的實現原理及偏向鎖、輕量級鎖、自旋鎖、公平鎖簡介

    在多執行緒程式設計中,synchronized 一直都是元老級別的存在,很多人都稱之為重量級鎖。本文來簡單介紹synchronized的實現原理,以及為減少獲得鎖和釋放鎖所帶來的效能損耗而引進的偏向鎖與輕量級鎖。

    Java中使用synchronized來實現同步的基礎是什麼呢?Java中每一個物件都可以作為鎖!

        對於普通同步方法,鎖是當前例項物件。

        對於靜態同步方法,鎖是當前類的Class物件。

        對於同步方法塊,鎖是synchronized括號裡配置的物件。

    當一個執行緒想要訪問同步程式碼塊時,它首先必須先得到鎖,退出或丟擲異常時需要釋放鎖。那麼鎖到底存在於哪裡呢?

synchronized實現原理

    先來看一下synchronized的實現原理,JVM基於進入和退出Monitor物件來實現方法和程式碼塊同步,使用monitorenter和monitorexit指令實現。monitorenter指令是在編譯後插入到同步程式碼塊的開始位置,而monitorexit是插入待方法結束處和異常處,JVM要保證每一個monitorenter必須有一個monitorexit與之對應。任何一個物件都有一個monitor與之關聯,當一個monitor被持有後,它將處於鎖定狀態,當執行緒執行到monitorenter處時,會嘗試獲取monitor物件,即嘗試獲得物件的鎖!

Java物件頭

    synchronized用的鎖是存在Java物件頭裡的。

    Java物件頭裡的Mark Word 裡預設儲存物件的HashCode、分代年齡和鎖標記位。

鎖的升級與自旋鎖、公平鎖

    Java 1.6後為了減少獲得鎖與釋放鎖所帶來的效能損耗,引入了偏向鎖與輕量級鎖,在Java1.6中鎖一共有四種狀態,分別是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,這幾種狀態會隨著競爭情況逐漸升級。鎖狀態只能升級而不能降級。

    偏向鎖

        HotSpot的作者經過研究發現,大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由一個執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖

。當一個執行緒訪問同步塊並獲取鎖時,會在物件頭和棧幀中儲存鎖偏向的執行緒ID,以後該執行緒再進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需要簡單的測試一下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖標識是否設定成1(標識當前是偏向鎖):如果沒有設定,則使用CAS競爭鎖;如果設定了,則嘗試使用CAS將物件頭偏向鎖指向當前執行緒。

    自旋鎖

        在多執行緒條件下,由於Java的執行緒與系統核心執行緒是一一對應的,所以掛起執行緒和恢復執行緒的操作都需要轉到核心中完成,這會給系統的併發效能帶來很大的壓力。同時,在許多應用上,共享資料的鎖定狀態只會持續很短的時間,為了這段時間掛起恢復執行緒並不值得。如果機器上能讓兩個或兩個以上的執行緒同時執行,我們就可以讓後面請求鎖的執行緒“稍等一下”,但不放棄處理器的執行時間,看看持有鎖的執行緒是否很快會釋放鎖。為了讓執行緒等待,我們只需要讓執行緒執行一個忙迴圈(自旋),這項技術就是所謂的自旋鎖。

        當然,如果一個執行緒等待另一個執行緒釋放鎖,自旋的時間過長,那也將會浪費系統資源。JDK 1.6中引入了自適應的自旋鎖。自旋等待的時間不再固定,由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖物件上,自旋等待剛剛成功過,並且持有鎖的執行緒正在執行中,那麼虛擬機器將會認為這次自旋也很有可能會成功,進而它將允許自旋等待相對更長的時間。

    輕量級鎖

        輕量級鎖並不是來替代重量級鎖的,它是為了在沒有多執行緒競爭下,減少作業系統的效能消耗。

       在程式碼進入同步塊的時候,如果此物件沒有被鎖定,虛擬機器首先將在當前執行緒的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於儲存物件目前的Mark Word拷貝。當虛擬機器將使用CAS操作嘗試將物件的Mark Word更新為指向Lock Record的指標。如果這個動作成功了,那麼這個物件就獲得了物件的鎖。如果這個更新失敗了,虛擬機器將會先檢查物件的Mark Word是否指向當前的棧幀,如果是,則當前執行緒已經擁有了這個物件的鎖,否則說明這個鎖物件已經被其他執行緒搶佔了。如果有兩個以上的執行緒爭用同一個鎖,那麼輕量級鎖將會膨脹為重量級鎖。

        輕量級鎖能提升程式同步效能的根據是:對於絕大多數的鎖,在整個同步週期內都是不存在競爭的。

    公平鎖

        公平鎖是指多個執行緒在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖;非公平鎖不能保證這一點,在鎖被釋放時,任何一個等待的執行緒都有機會獲得鎖。synchronized中的鎖時非公平的,ReentrantLock預設情況下也是非公平的,可以通過建構函式要求使用公平鎖。

參考資料:《併發程式設計的藝術》、《深入理解Java虛擬機器》