ReentrantLock和synchronized的比較

ReentrantLock和synchronized的比較。Monitor是原理和作用。

開始

今天,我們來聊聊ReentrantLock和synchronized的相似與不同。

都是阻塞

ReentrantLock和synchronized都是加鎖式同步,當一個線程獲取了對象鎖後,其它要進入同步塊的線程就必須阻塞在同步塊外等待。線程的阻塞和喚醒需要操作系統在用戶態和內核態之間切換,所以,ReentrantLock和synchronized都是代價比較高的。

實現方式

synchronized是java語言的關鍵字,它的鎖機制是由jvm實現的,是原生語法層面上的互斥,最底層是mutex。而ReentrantLock則是JDK1.5之後,提供的api層面 的鎖,需要在代碼中顯示調用lock、unlock等方法來完成。

所以,從便利性來說,synchronized使用起來更簡單一些。但是從靈活度來說,ReentrantLock更靈活,可控性也更強,可實現更細粒度的鎖。但是,在使用ReentrantLock時,一定要注意lock和unlock的匹配和順序,否則就可能造成死鎖。常見的方案是把unlock放在異常處理的finally語句塊中。

性能

人們很容易被大眾化的觀點所誤導,認為synchronized的效率會比ReentrantLock差很多。但是事實上,synchronized在JDK的發展過程中,經過了不斷優化,比如引入了偏向鎖,輕量級鎖,鎖升級機制等,目前,已經和ReentrantLock的效率相差不多了。如果沒有特殊的場景,推薦使用synchronized,因為它使用起來比較簡單,且不會造成死鎖。

是否公平鎖

排隊等廁所,廁所門上有把鎖。裡面的人用完出來,把鑰匙給隊伍最前面的人,這就是公平鎖。如果裡面的人用完出來,把鑰匙直接扔地上,誰搶上算誰的,這就是非公平鎖。

synchronized是非公平鎖,並且它無法實現公平鎖。要實現公平鎖,可以通過ReentrantLock來實現。通過new ReentrantLock(true)可以用來構造一個公平鎖。

是否可重入鎖

一個線程可以對某個資源重複加鎖,稱之為可重入鎖。這個情形很常見於遞歸。如果鎖不可重入,就有可能會發生如下情況:

A線程獲取方法B的鎖,在方法B中,有代碼遞歸調用了自己。於是,A線程需要在方法B中再次獲取B的鎖。如果鎖不可重入,A就會發現,方法B上已經有鎖,A就進入了等待。但事實上,給B加鎖的就是A自己。自己一直在等待自己,豈不是可笑?

synchronized就是一把可重入鎖。當然了,使用ReentrantLock也可以實現可重入鎖。

Monitor

ReentrantLock和synchronized的比較


同步塊

上圖是一個同步塊在執行時的基本示意圖。單位時間裡,只能有一個線程在同步塊中。那麼,一個線程在獲得了同步塊的執行權(即鎖)之後,是否能夠一直執行到線程完畢呢?我們說,可能不行。因為對某個資源的鎖是有一個優先級的。正在執行的線程可能需要讓位給優先級更高的線程,此時,當前線程就會進入等待區。所以,上圖的,我們發現,有兩塊區域都是為等待的線程設置的,一個是Entry Set,另一個是Wait Set。假設當前同步塊有代碼正在運行,那麼,新進入的線程就會進入Entry Set,被擠佔被迫等待的線程,則會進入Wait Set。

圖中還有一個含義是,當同步塊中的線程執行完畢,退出同步塊後,Entry Set和Wait Set中的線程會共同競爭,以獲得同步塊的執行權,即獲取鎖。

Monitor,直譯為監視器,底層實現是Mutex。JVM會給每個對象和class字節碼設置一個monitor。當某個線程要進入某個同步塊是,就需要獲得對應目標的monitor。換句話,當某個線程獲得了對應目標的monitor,它就進入了同步塊。當該線程執行完同步塊,或被擠佔而等待時,就會讓出monitor。

synchronized就是利用monitor來實現的。

等待可中斷

等待可中斷是使用ReentrantLock時,可以實現的一個機制。當某個線程等待鎖過長時間時,程序可以通過lockInterruptibly方法來使當前線程中斷等待,轉去執行其它的線程。

線程分組喚醒

有些場景下,我們可能不希望喚醒所有的線程,而是喚醒部分線程。這種方式在synchronized下是無法實現的。但是,ReentrantLock通過提供一個Contition類,可以同時綁定多個對象,以此,來實現線程的分組喚醒。


分享到:


相關文章: