ReentrantLock和synchronized的區別

Java 5爲了增強內置鎖的功能,引入了可重入鎖(ReentrantLock)。在此以前「synchronized」和「volatile」是實現併發的方式。html

Synchronized關鍵字使用內置鎖(intrinsic lock)或者稱做監視鎖(monitor lock)。每個Java對象都有一個內置鎖與之相關聯。不管何時,當一個線程嘗試去訪問一個synchronized代碼塊或者synchronized方法的時候,線程都須要首先獲取到對象關聯的內置鎖。對於static方法,線程獲取的是類對象的內置鎖。java

內置鎖機制使得代碼的書寫很是整潔,而且大部分場合下功能也夠用。因此爲何咱們須要額外的去顯式的建立鎖?讓咱們討論一下。併發

內置鎖機制在功能上有一些限制:性能

  1. 不能中斷(interrupt)一個正在等待獲取鎖的線程
  2. 不能測試鎖是否空閒從而不用一直等待鎖
  3. 不能實現非代碼塊結構的加鎖與釋放鎖。由於內置鎖必須在和獲取鎖相同的代碼塊釋放鎖。

除此以外,ReentrantLock還支持對鎖的測試,支持既能夠被中斷又能夠設置超時。ReentrantLock還能夠設置公平原則,容許更靈活的線程調度。測試

讓咱們看一下實現了Lock類的ReentrantLock類中的部分方法:線程

 

讓咱們理解一下如何使用上面的分析結果,看看咱們會獲得什麼好處。code

 

輪詢和設置了超時的對鎖的獲取htm

讓咱們看一段代碼的例子:對象

 

在上面的方法中,當兩個線程A和B幾乎在同時轉帳(transfer money)的時候有可能會發生死鎖。blog

 

有可能線程A已經獲取了acc1對象的鎖而且正在等待獲取acc2對象的鎖,與此同時,線程B已經獲取了acc2對象的鎖而且正在等待獲取acc1的鎖。這將會致使死鎖,程序不得不須要重啓!!

然而有一個方法能夠避免這種狀況:就是所謂的按相同的順序獲取鎖。我我的以爲這個實現起來比較困難。

一個更簡便的實現方式是用ReentrantLock的tryLock()方法。這種方法稱爲「輪詢式可超時的鎖獲取」。即使你不可以獲取全部必須的鎖,它也可使你從新得到對程序的控制,釋放部分已經獲取的鎖,而後從新嘗試。

所以,咱們將會使用trylock()來獲取兩個鎖,若是咱們不能獲取到兩個鎖,能夠釋放已經獲取到的一個,而後重試。

 

這裏咱們實現了一個支持超時的加鎖機制,因此,若是鎖在指定的時間段內不能被獲取到,方法將會返回失敗(false),優雅的退出。

 

獲取鎖能夠被中斷

獲取鎖能夠被中斷使得鎖可使用在能夠取消的操做上。

lockInterruptibly()方法使得咱們能夠嘗試去獲取鎖可是保留線程能夠被中斷的能力。基本的意思是這個方法使得線程能夠當即響應從其它線程發過來的中斷信號。這在當咱們想要發送中斷信號到全部等待鎖的線程時會頗有用。

讓咱們看一個例子,假設咱們有一個共享的方法來發送消息,但願若是有其餘的線程請求中斷,那麼正在發送的線程應當釋放鎖而且退出或者中止正在進行的操做以取消當前的任務。

 

帶超時的tryLock(long time, TimeUnit unit)方法也是能夠響應中斷的。

 

非代碼塊加鎖

內置鎖的獲取和釋放是以代碼塊的結構出現的,即鎖老是在被獲取的同一個代碼塊被釋放,無論程序邏輯如何。

外置鎖提供了更加顯式的控制。在一些哈希容器和鏈表中使用到了外置鎖。

 

公平性

ReentrantLock的構造方法能夠設置是否使用公平原則:建立一個公平鎖或者非公平鎖。使用公平鎖的線程們將會以他們請求獲取鎖的順序獲得鎖,而非公平鎖容許線程不按請求順序獲取鎖,這稱做「闖入」(當鎖空閒時,打破隊列順序去獲取鎖)。

公平鎖由於會涉及到線程的掛起和恢復因此有很大的性能方面的代價。可能會有如下的狀況:從一個掛起的線程被恢復到它開始實際執行會有嚴重的延時。讓咱們看一個情形:

A -> 持有鎖

B -> 請求鎖,等待A釋放鎖,而後進入了掛起狀態

C -> 請求鎖,同時A釋放了鎖,此時C並無進入掛起狀態

因爲C並無處於掛起狀態,因此它是有機會先去獲取A釋放的鎖,完成工做,而後甚至在線程B被喚醒以前就釋放鎖的。所以,這種情形下,非公平鎖具備很是大的性能上的優點。

 

結篇

內置鎖和外置鎖的內部實現機制是同樣的,因此性能的提高是主觀上的。它依賴於咱們上面討論的具體狀況。外置鎖提供了對死鎖,線程飢餓等問題的顯示的處理方式。

 

做者公衆號(碼年)掃碼關注:

英文原文:

https://www.javacodegeeks.com/2013/11/what-are-reentrant-locks.html

相關文章
相關標籤/搜索