国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

線程間的同步與通信(3)——淺析synchronized的實現原理

keithxiaoy / 538人閱讀

摘要:由此可見,自旋鎖和各有優劣,他們分別適用于競爭不多和競爭激烈的場景中。每一個試圖進入同步代碼塊的線程都會被封裝成對象,它們或在對象的中,或在中,等待成為對象的成為的對象即獲取了監視器鎖。

前言

系列文章目錄

前面兩篇文章我們介紹了synchronized同步代碼塊以及wait和notify機制,大致知道了這些關鍵字和方法是干什么的,以及怎么用。

但是,知其然,并不知其所以然。

例如:

什么是監視器鎖?

JAVA中任何對象都可以作為鎖,那么鎖信息是怎么被記錄和存儲的?

監視器鎖是怎樣被獲取的?

監視器鎖是怎樣被釋放的?

什么是wait set

本篇我們將來解答這些問題。

spin-lock 和 suspend-lock

總的來說,鎖有兩種不同的實現方式,一種是自旋,一種是掛起。

(suspend-lock不知道怎么翻譯,感覺叫"掛起鎖"或"懸掛鎖"都太難聽了,后面就直接不翻譯了)

自旋鎖是一種樂觀鎖,它樂觀地認為鎖資源沒有被占用,或者即使被占用了,也很快就會被釋放, 所以當它發現鎖已經被占用后,大多會在原地忙等待(一般是在死循環中等待,這也就是自旋的由來), 直到鎖被釋放,我們在之前分析AQS的文章中提過,AQS處在阻塞隊列頭部的線程用的就是自旋的方式來等待鎖。

suspend-lock是一種悲觀鎖,它悲觀地認為鎖競爭總是經常發生的,如果鎖被占用了,基本短時間內不會釋放,所以他會讓出CPU資源,直接掛起,等待條件滿足后,別人將自己喚醒。

自旋鎖的優點是實現簡單,只需要很小的內存,在競爭不多的場景中性能很好。但是如果鎖競爭很多,那么大量的時間會浪費在無意義的自旋等待上,造成CPU利用率降低。

suspend-lock的優點是CPU利用率高,因為在發現鎖被占用后,它會立即釋放自己剩下的CPU時間隙(time-slice)給其他線程,以期望獲得更高的CPU利用率。但是因為線程的掛起與喚醒需要通過操作系統調用來完成,這涉及到用戶空間和內核空間的轉換,線程上下文的切換,所以即使在競爭很少的場景中,這種鎖也會顯得很慢。但是如果鎖競爭很激烈,則這種鎖就可以獲得很好的性能。

由此可見,自旋鎖和suspend-lock各有優劣,他們分別適用于競爭不多和競爭激烈的場景中。

在實際的應用中,我們可以綜合這兩種方式的優點,例如AQS中,排在阻塞隊列第一位的使用自旋等待,而排在后面的線程則掛起。

而我們今天要講的synchronized,使用的是suspend-lock方式。

synchronized的實現

既然前面提到了synchronized用的是suspend-lock方式,在看synchronized的實現原理之前,我們不妨來思考一下: 如果要我們自己設計,該怎么做?

前幾篇我們提到過:

每個java對象都可以用做一個實現同步的鎖, 這些鎖被稱為內置鎖(Intrinsic Lock)或者監視器鎖(Monitor Lock).

要實現這個目標,則每個java對象都應該與某種類型的鎖數據關聯。

這就意味著,我們需要一個存儲鎖數據的地方,并且每一個對象都應該有這么個地方。

在java中,這個地方就是對象頭。

其實Java的對象頭和對象的關系很像Http請求的http headerhttp body的關系。

對象頭中存儲了該對象的metadata, 除了該對象的鎖信息,還包括指向該對象對應的類的指針,對象的hashcode, GC分代年齡等,在對象頭這個寸土寸金的地方,根據鎖狀態的不同,有些內存是大家公用的,在不同的鎖狀態下,存儲不同的信息,而對象頭中存儲鎖信息的那部分字段,我們稱作Mark Word, 這個我們就不展開了講了。我們只需要知道:

鎖信息存儲在對象頭的Mark Word

在synchronized鎖中,這個存儲在對象頭的Mark Word中的鎖信息是一個指針,它指向一個monitor對象(也稱為管程或監視器鎖)的起始地址。這樣,我們就通過對象頭,將每一個對象與一個monitor關聯了起來,它們的關系如下圖所示:


(圖片來源: Evaluating and improving biased locking in the HotSpot virtual machine)

圖片的最左邊是線程的調用棧,它引用了堆中的一個對象,該對象的對象頭部分記錄了該對象所使用的監視器鎖,該監視器鎖指向了一個monitor對象。

那么這個monitor對象是什么呢? 在Java虛擬機(HotSpot)中,monitor是由ObjectMonitor實現的,其主要數據結構如下: (源碼在這里)

 ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
 }

上面這些字段中,我們只需要重點關注三個字段:

_owner : 當前擁有該 ObjectMonitor 的線程

_EntryList: 當前等待鎖的集合

_WaitSet: 調用了Object.wait()方法而進入等待狀態的線程的集合,即我們上一篇一直提到的wait set

在java中,每一個等待鎖的線程都會被封裝成ObjectWaiter對象,當多個線程同時訪問一段同步代碼時,首先會被扔進 _EntryList 集合中,如果其中的某個線程獲得了monitor對象,他將成為 _owner,如果在它成為 _owner之后又調用了wait方法,則他將釋放獲得的monitor對象,進入 _WaitSet集合中等待被喚醒。

(圖片來源: Inter-thread communication in Java)

另外,因為每一個對象都可以作為synchronized的鎖,所以每一個對象都必須支持wait()notifynotifyAll方法,使得線程能夠在一個monitor對象上wait, 直到它被notify。這也就解釋了這三個方法為什么定義在了Object類中——這樣,所有的類都將持有這三個方法。

總結:

每一個java對象都和一個ObjectMonitor對象相關聯,關聯關系存儲在該java對象的對象頭里的Mark Word中。

每一個試圖進入同步代碼塊的線程都會被封裝成ObjectWaiter對象,它們或在ObjectMonitor對象的_EntryList中,或在 _WaitSet 中,等待成為ObjectMonitor對象的owner. 成為owner的對象即獲取了監視器鎖。

所以,說是每一個java對象都可以作為鎖,其實是指將每一個java對象所關聯的ObjectMonitor作為鎖,更進一步是指,大家都想成為 某一個java對象所關聯的ObjectMonitor對象的_owner,所以你可以把這個_owner看做是鐵王座,所有等待在這個監視器鎖上的線程都想坐上這個鐵王座,誰擁有了它,誰就有進入由它鎖住的同步代碼塊的權利。

附加題

其實,了解到上面這個程度已經足夠用了,如果你想再深入的了解,例如synchronized在字節碼層面的具體語義實現,這里推薦幾篇博客:

Moniter的實現原理

OpenJDK9 Hotspot : synchronized 淺析

深入理解Java并發之synchronized實現原理

死磕Java并發—–深入分析synchronized的實現原理

另外,如果你想深入了解偏向鎖,輕量級鎖,以及鎖膨脹的過程,強烈建議看下面這篇論文:

Evaluating and improving biased locking in the HotSpot virtual machine

該篇論文的介紹非常詳細,關鍵是有很多圖示,對于Mark Word在不同鎖狀態的描述很清晰。

(完)

查看更多系列文章:系列文章目錄

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76728.html

相關文章

  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學習經歷。因為寫作的時候發現,為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...

    lijy91 評論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學習經歷。因為寫作的時候發現,為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...

    Yumenokanata 評論0 收藏0
  • java 多線程編程核心技術 3線程通信

    摘要:在從返回前,線程與其他線程競爭重新獲得鎖。就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程。當線程呈狀態,調用線程對象的方法會出現異常。在執行同步代碼塊過程中,遇到異常而導致線程終止,鎖也會被釋放。 方法wait()的作用是使當前執行代碼的線程進行等待,wait()方法是Object類的方法,該方法用來將當前線程置入預執行隊列中,并且在wait()所在的代碼行處停止執行,直...

    Dogee 評論0 收藏0
  • 線程同步通信(1)——同步代碼塊Synchronized

    摘要:前言同步代碼塊是中最基礎的實現線程間的同步與通信的機制之一,本篇我們將對同步代碼塊以及監視器鎖的概念進行討論。離開同步代碼塊后,所獲得的鎖會被自動釋放。 前言 同步代碼塊(Synchronized Block) 是java中最基礎的實現線程間的同步與通信的機制之一,本篇我們將對同步代碼塊以及監視器鎖的概念進行討論。 系列文章目錄 什么是同步代碼塊(Synchronized Block)...

    Gu_Yan 評論0 收藏0
  • Java 多線程核心技術梳理(附源碼)

    摘要:本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,的使用,定時器,單例模式,以及線程狀態與線程組。源碼采用構建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。 本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,lock的使用,定時器,單例模式,以及線程狀態與線程組。 寫在前面 花了一周時...

    Winer 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<