JAVA ReentrantLock 分析
并發編程中經常用到的莫非是這個ReentrantLock這個類,線程獲取鎖和釋放鎖。還有一個則是synchronized,常用來多線程控制獲取鎖機制。
先寫一個簡單的例子。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package?com.multi.thread; import?java.util.concurrent.locks.Lock; import?java.util.concurrent.locks.ReentrantLock; public?class?AQSDemo?{ ????public?static?void?main(String[]?args)?{ ????????Lock?lock?=?new?ReentrantLock(true); ????????MyThread?t1?=?new?MyThread("t1",?lock); ????????MyThread?t2?=?new?MyThread("t2",?lock); ????????MyThread?t3?=?new?MyThread("t3",?lock); ????????t1.start(); ????????t2.start(); ????????t3.start(); ????} ????? } class?MyThread?extends?Thread?{ ????private?Lock?lock; ????public?MyThread(String?name,?Lock?lock)?{ ????????super(name); ????????this.lock?=?lock; ????} ????@Override ????public?void?run()?{ ????????lock.lock(); ????????try?{ ????????????System.out.println(Thread.currentThread()?+?"??is?running?"); ????????????try?{ ????????????????Thread.sleep(500); ????????????}?catch?(InterruptedException?e)?{ ????????????????e.printStackTrace(); ????????????} ????????}?finally?{ ????????????lock.unlock(); ????????} ????} } |
這是個簡單的用ReentrantLock的代碼。
知識點理解:
ReentranctLock:
1) 可重入性:大致意思就是如果一個函數能被安全的重復執行,那么這個函數是可重復的。聽起來很繞口。
2)可重入鎖:一個線程可以重復的獲取它已經擁有的鎖。
特性:
1)ReentrantLock可以在不同的方法中使用。
2)支持公平鎖和非公平鎖概念
????static final class NonfairSync extends Sync;(非公平鎖)
????static final class FairSync extends Sync;(公平鎖)
3)支持中斷鎖,收到中斷信號可以釋放其擁有的鎖。
4)支持超時獲取鎖:tryLock方法是嘗試獲取鎖,支持獲取鎖的是帶上時間限制,等待一定時間就會返回。
ReentrantLock就先簡單說一下AQS(AbstractQueuedSynchronizer)。java.util.concurrent包下很多類都是基于AQS作為基礎開發的,Condition,BlockingQueue以及線程池使用的worker都是基于起實現的,其實就是將負雜的繁瑣的并發過程封裝起來,以便其他的開發工具更容易的開發。其主要通過volatile和Unsafe類的原子操作,來實現阻塞和同步。
AQS是一個抽象類,其他類主要通過重載其tryAcquire(int arg)來獲取鎖,和tryRelease來釋放鎖。
AQS不在這里做分析,會有單獨的一篇文章來學習AQS。
ReentrantLock類里面主要有三個類,Sync,NonfairSync,FairSync這三個類,NonfairSync與FairSync類繼承自Sync類,Sync類繼承自AbstractQueuedSynchronizer抽象類。
Sync是ReentrantLock實現公平鎖和非公平鎖的主要實現,默認情況下ReentrantLock是非公平鎖。
????Lock lock = new ReentrantLock(true); ? :true則是公平鎖,false就是非公平鎖,什么都不傳也是非公平鎖默認的。
非公平鎖:
????lock.lock();點進去代碼會進入到,ReentranctLock內部類Sync。
????
| 1 2 3 4 5 6 7 8 9 10 11 | ????abstract?static?class?Sync?extends?AbstractQueuedSynchronizer?{ ????????private?static?final?long?serialVersionUID?=?-5179523762034025860L; ????????/** ?????????*?Performs?{@link?Lock#lock}.?The?main?reason?for?subclassing ?????????*?is?to?allow?fast?path?for?nonfair?version. ?????????*/ ????????abstract?void?lock(); ????????? ????????......省略。 ??????} |
這個抽象類Sync的里有一個抽象方法,lock(),供給NonfairSync,FairSync這兩個實現類來實現的。這個是一個模板方法設計模式,具體的邏輯供給子類來實現。
非公平鎖的lock的方法,雖然都可以自己看,但是還是粘貼出來,說一下。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ?static?final?class?NonfairSync?extends?Sync?{ ????????private?static?final?long?serialVersionUID?=?7316153563782823691L; ????????/** ?????????*?Performs?lock.??Try?immediate?barge,?backing?up?to?normal ?????????*?acquire?on?failure. ?????????*/ ????????final?void?lock()?{ ????????????if?(compareAndSetState(0,?1)) ????????????????setExclusiveOwnerThread(Thread.currentThread()); ????????????else ????????????????acquire(1); ????????} ????????......省略 ????} |
其實重點看這個compareAndSetState(0,1),這個其實一個原子操作,是cas操作來獲取線程的資源的。其代表的是如果原來的值是0就將其設為1,并且返回true。其實這段代碼就是設置private volatile int state;,這個狀態的。
其實現原理就是通過Unsafe直接得到state的內存地址然后直接操作內存的。設置成功,就說明已經獲取到了鎖,如果失敗的,則會進入:
| 1 2 3 4 5 | public?final?void?acquire(int?arg)?{ ????????if?(!tryAcquire(arg)?&& ????????????acquireQueued(addWaiter(Node.EXCLUSIVE),?arg)) ????????????selfInterrupt(); ????} |
這個方法里,這個過程是先去判斷鎖的狀態是否為可用,如果鎖已被持有,則再判斷持有鎖的線程是否未當前線程,如果是則將鎖的持有遞增,這也是java層實現可重入性的原理。如果再次失敗,則進入等待隊列。就是要進去等待隊列了AQS有一個內部類,是Node就是用來存放獲取鎖的線程信息。
AQS的線程阻塞隊列是一個雙向隊列,提供了FiFO的特性,Head節點表示頭部,tail表示尾部。
1)節點node,維護一個volatile狀態,維護一個prev指向向前一個隊列節點,根據前一個節點的狀態來判斷是否獲取鎖。
2)當前線程釋放的時候,只需要修改自身的狀態即可,后續節點會觀察到這個volatile狀態而改變獲取鎖。volatile是放在內存中的,共享的,所以前一個節點改變狀態后,后續節點會看到這個狀態信息。
獲取鎖失敗后就會加入到隊列里,但是有一點,不公平鎖就是,每個新來的線程來獲取所得時候,不是直接放入到隊列尾部,而是也去cas修改state狀態,看看是否獲取鎖成功。
總結非公平鎖:
首先會嘗試改變AQS的狀態,改變成功了就獲取鎖,否則失敗后再次通過判斷當前的state的狀態是否為0,如果為0,就再次嘗試獲取鎖。如果state不為0,該鎖已經被其他線程持有了,但是其它線程也可能也是自己啊,所以也要判斷一下是否是自己獲取線程,如果是則是獲取成功,且鎖的次數要加1,這是可重入鎖,不是則加入到node阻塞隊列里。加入到隊列后則在for循環中通過判斷當前線程狀態來決定是否喲啊阻塞。可以看出在加入隊列前及阻塞前多次嘗試去獲取鎖,而避免進入線程阻塞,這是因為阻塞、喚醒都需要cpu的調度,以及上下文切換,這是個重量級的操作,應盡量避免。
公平鎖:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | FairSync類: final?void?lock()?{ ???//先去判斷鎖的狀態,而不是直接去獲取 ????acquire(1); } AQS類: public?final?void?acquire(int?arg)?{ ????if?(!tryAcquire(arg)?&& ????????acquireQueued(addWaiter(Node.EXCLUSIVE),?arg)) ????????selfInterrupt(); } FairSync類: protected?final?boolean?tryAcquire(int?acquires)?{ ????final?Thread?current?=?Thread.currentThread(); ????int?c?=?getState(); ????if?(c?==?0)?{ ????? ????//hasQueuedPredecessors判斷是否有前節點,如果有就不會嘗試去獲取鎖 ????????if?(!hasQueuedPredecessors()?&& ????????????compareAndSetState(0,?acquires))?{ ????????????setExclusiveOwnerThread(current); ????????????return?true; ????????} ????} ????else?if?(current?==?getExclusiveOwnerThread())?{ ????????int?nextc?=?c?+?acquires; ????????if?(nextc?<?0) ????????????throw?new?Error("Maximum?lock?count?exceeded"); ????????setState(nextc); ????????return?true; ????} ????return?false; } |
公平鎖,主要區別是:什么事都要有個先來后到,先來的有先。獲取鎖的時候是先看鎖是否可用并且是否有節點,就是是否有阻塞隊列。有的話,就是直接放入到隊列尾部,而不是獲取鎖。
釋放鎖:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public?void?unlock()?{ ????sync.release(1); } public?final?boolean?release(int?arg)?{ ????if?(tryRelease(arg))?{ ????????Node?h?=?head; ????????if?(h?!=?null?&&?h.waitStatus?!=?0) ????????????unparkSuccessor(h); ????????return?true; ????} ????return?false; } protected?final?boolean?tryRelease(int?releases)?{ ????int?c?=?getState()?-?releases; ????if?(Thread.currentThread()?!=?getExclusiveOwnerThread()) ????????throw?new?IllegalMonitorStateException(); ????boolean?free?=?false; ????if?(c?==?0)?{ ????????free?=?true; ????????setExclusiveOwnerThread(null); ????} ????setState(c); ????return?free; } |
釋放鎖是很簡單的,就是先去改變state這個狀態的值,改變后如果狀態為0,則說明釋放成功了,如果直接可重入了多次,也要釋放很多次的鎖。
釋放過程:
Head節點就是當前持有鎖的線程節點,當釋放鎖時,從頭結點的next來看,頭結點的下一個節點如果不為null,且waitStatus不大于0,則跳過判斷,否則從隊尾向前找到最前的一個waitStatus的節點,然后通過LockSupport.unpark(s.thread)喚醒該節點線程。可以看出ReentrantLock的非公平鎖只是在獲取鎖的時候是非公平的,如果進入到等待隊列后,在head節點的線程unlock()時,會按照進入的順序來得到喚醒,保證了隊列的FIFO的特性。
參考文章:
http://silencedut.com/2017/01/09/%E7%94%B1ReentrantLock%E5%88%86%E6%9E%90JUC%E7%9A%84%E6%A0%B8%E5%BF%83AQS/
http://www.cnblogs.com/leesf456/p/5383609.html
https://github.com/pzxwhc/MineKnowContainer/issues/16
http://www.importnew.com/24006.html
本文轉自 豆芽菜橙 51CTO博客,原文鏈接:http://blog.51cto.com/shangdc/1930644
總結
以上是生活随笔為你收集整理的JAVA ReentrantLock 分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信怎么发文字朋友圈
- 下一篇: 如何诊断ORA-125XX连接问题