1. 程式人生 > >多執行緒——併發鎖的集結號

多執行緒——併發鎖的集結號

      上次我們說了一下多執行緒的管理,今天我們來看一下多執行緒的鎖。

這裡寫圖片描述

1、不同的加鎖方式

類鎖

      在程式碼中的方法上加了static和synchronized的鎖,或者synchronized(xxx.class)的程式碼段

物件鎖

      在程式碼中的方法上加了synchronized的鎖,或者synchronized(this)的程式碼段

私有鎖

      在類內部宣告一個私有屬性如private Object lock,在需要加鎖的程式碼段synchronized(lock)

注: 類鎖和物件鎖不會產生競爭,二者的加鎖方法不會相互影響。
私有鎖和物件鎖也不會產生競爭,二者的加鎖方法不會相互影響。

2、加鎖的時間

悲觀鎖

      一段執行邏輯加上悲觀鎖,不同執行緒同時執行時,只能有一個執行緒執行,其他的執行緒在入口處等待,直到鎖被釋放.

樂觀鎖

      一段執行邏輯加上樂觀鎖,不同執行緒同時執行時,可以同時進入執行,在最後更新資料的時候要檢查這些資料是否被其他執行緒修改了(版本和執行初是否相同),沒有修改則進行更新,否則放棄本次操作.

讀寫鎖

      讀寫鎖即是針對於讀寫操作的互斥鎖。多個讀者可以同時進行讀,寫者必須互斥,寫者優先於讀者

3、是否順序

公平鎖

      指的是哪個執行緒先執行,那就可以先得到鎖。

非公平鎖

      是不管執行緒是否是先執行,都是隨機獲得鎖的。

4、 執行緒間

互斥鎖

      只要被鎖住,其他任何執行緒都不可以訪問被保護的資源

阻塞鎖

      可以說是讓執行緒進入阻塞狀態進行等待,當獲得相應的訊號(喚醒,時間) 時,才可以進入執行緒的準備就緒狀態,準備就緒狀態的所有執行緒,通過競爭,進入執行狀態。

可重入鎖

      也叫做遞迴鎖,指的是同一執行緒 外層函式獲得鎖之後 ,內層遞迴函式仍然有獲取該鎖的程式碼,但不受影響。

自旋鎖

      可以使執行緒在沒有取得鎖的時候,不被掛起,而轉去執行一個空迴圈,(即所謂的自旋,就是自己執行空迴圈),若在若干個空迴圈後,執行緒如果可以獲得鎖,則繼續執行。若執行緒依然不能獲得鎖,才會被掛起。

訊號量鎖

      從概念上說,訊號量(semaphore)是原子化(automically)遞增和遞減的非負整數。如果一個執行緒試圖遞減一個訊號量,但這個訊號量的值已經為0,則執行緒將會阻塞。另一個執行緒“發出(post)”這個訊號(semaphore),使用訊號量大於0之後,被阻塞的執行緒才會被釋放。

5、鎖變化

鎖的狀態總共有四種

      無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。JDK 1.6中預設是開啟偏向鎖和輕量級鎖的,

鎖膨脹

      從輕量鎖膨脹到重量級鎖是在輕量級鎖解鎖過程發生的。

重量級鎖

      Synchronized是通過物件內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴於底層的作業系統的Mutex Lock來實現的。而作業系統實現執行緒之間的切換這就需要從使用者態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什麼Synchronized效率低的原因。因此,這種依賴於作業系統Mutex Lock所實現的鎖我們稱之為“重量級鎖”。

輕量級鎖

      “輕量級”是相對於使用作業系統互斥量來實現的傳統鎖而言的。但是,首先需要強調一點的是,輕量級鎖並不是用來代替重量級鎖的,它的本意是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用產生的效能消耗。在解釋輕量級鎖的執行過程之前,先明白一點,輕量級鎖所適應的場景是執行緒交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

偏向鎖 

      引入偏向鎖是為了在無多執行緒競爭的情況下儘量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令(由於一旦出現多執行緒競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的效能損耗必須小於節省下來的CAS原子指令的效能消耗)。上面說過,輕量級鎖是為了線上程交替執行同步塊時提高效能,而偏向鎖則是在只有一個執行緒執行同步塊時進一步提高效能。

無鎖狀態

      在程式碼進入同步塊的時候,如果同步物件鎖狀態為無鎖狀態。

鎖削除

      是指虛擬機器即時編譯器在執行時,對一些程式碼上要求同步,但是被檢測到不可能存在共享資料競爭的鎖進行削除。鎖削除的主要判定依據來源於逃逸分析的資料支援,如果判斷到一段程式碼中,在堆上的所有資料都不會逃逸出去被其他執行緒訪問到,那就可以把它們當作棧上資料對待,認為它們是執行緒私有的,同步加鎖自然就無須進行

鎖粗化

      原則上,我們在編寫程式碼的時候,總是推薦將同步塊的作用範圍限制得儘量小——只在共享資料的實際作用域中才進行同步,這樣是為了使得需要同步的運算元量儘可能變小,如果存在鎖競爭,那等待鎖的執行緒也能儘快地拿到鎖。
  大部分情況下,上面的原則都是正確的,但是如果一系列的連續操作都對同一個物件反覆加鎖和解鎖,甚至加鎖操作是出現在迴圈體中的,那即使沒有執行緒競爭,頻繁地進行互斥同步操作也會導致不必要的效能損耗。
  如果虛擬機器探測到有這樣一串零碎的操作都對同一個物件加鎖,將會把加鎖同步的範圍擴充套件(膨脹)到整個操作序列的外部,這樣只需要加鎖一次就可以了。

總結:

      今天我們總結了一下多執行緒中的鎖,以後我們會繼續分享關於鎖的學習。