干貨ReentrantLock非公平鎖源碼分析
本文轉(zhuǎn)載自微信公眾號(hào)「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java極客技術(shù)公眾號(hào)。
1.鎖
java中,加鎖的方式
- synchronized,這個(gè)是 java 底層實(shí)現(xiàn)的,也就是 C 語(yǔ)言實(shí)現(xiàn)的。
- . lock,這個(gè)是 java.util.concurrent 包下面的,是 java語(yǔ)言實(shí)現(xiàn)的。
2.ReentrantLock
ReentrantLock 是 Lock 的一種實(shí)現(xiàn),是一種可重入的公平或非公平鎖。默認(rèn)是非公平鎖。
2.1 Lock的創(chuàng)建
首先看下鎖的創(chuàng)建和使用代碼:
- //創(chuàng)建鎖
- Lock lock = new ReentrantLock();
- //加鎖
- lock.lock();
- //釋放鎖
- lock.unlock();
然后看下創(chuàng)建的是 ReentrantLock 的構(gòu)造函數(shù):
- public ReentrantLock() {
- sync = new NonfairSync();
- }
NonfairSync 就是非公平鎖。所以 ReentrantLock 默認(rèn)是非公平鎖的實(shí)現(xiàn)
2.2 lock()
加鎖的邏輯就比較復(fù)雜了,因?yàn)榇嬖诰€程競(jìng)爭(zhēng)。所以有兩種情況,一種是競(jìng)爭(zhēng)到鎖的處理,一種是沒(méi)有競(jìng)爭(zhēng)到鎖的處理。
首先我們還是來(lái)看下 lock() 方法,因?yàn)樽罱K是非公平的實(shí)現(xiàn),所以直接看 NonfairSync 里面的 lock 方法。
- final void lock() {
- if (compareAndSetState(0, 1))
- setExclusiveOwnerThread(Thread.currentThread());
- else
- acquire(1);
- }
2.3 沒(méi)有獲取到鎖的邏輯 acquire()
直接上代碼:
- public final void acquire(int arg) {
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }
還是3個(gè)方法,阿粉一個(gè)一個(gè)的說(shuō)。
tryAcquire(arg) ,還是先看代碼在分析。
- final boolean nonfairTryAcquire(int acquires) {
- final Thread current = Thread.currentThread();
- int c = getState();
- if (c == 0) {
- if (compareAndSetState(0, acquires)) {
- setExclusiveOwnerThread(current);
- return true;
- }
- }
- else if (current == getExclusiveOwnerThread()) {
- int nextc = c + acquires;
- if (nextc < 0) // overflow
- throw new Error("Maximum lock count exceeded");
- setState(nextc);
- return true;
- }
- return false;
- }
a. 獲取 state ,如果等于0,說(shuō)明之前獲得鎖的線程已經(jīng)釋放了,那么這個(gè)線程就會(huì)再次去競(jìng)爭(zhēng)鎖,這就是非公平鎖的體現(xiàn),如果是公平鎖,是沒(méi)有這個(gè)判斷的。
b. 如果前一個(gè)獲得鎖的線程沒(méi)有釋放鎖,那么就判斷是否是同一個(gè)線程,是的話就會(huì)將 state 加 1。這個(gè)就是重入鎖的體現(xiàn)。
c. 如果都不滿足,那么返回 false。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) ,再次獲取鎖沒(méi)有成功,并且又不是可重入鎖,那么就存入一個(gè)阻塞隊(duì)列里面。里面還有一點(diǎn)邏輯,就不展開了,有興趣可以自己看下。
selfInterrupt(); 這個(gè)是當(dāng)前線程的中斷標(biāo)志,作用就是在線程在阻塞的是否,客戶端通過(guò)調(diào)用了中斷線程的方法 interrupt(),那么該線程被喚醒的時(shí)候,就會(huì)有響應(yīng)的處理。具體要看這個(gè)線程 run 方法里面的代碼邏輯。
2.4 unlock()
- 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 - 1,如果大于0,說(shuō)明釋放的是重入鎖,只需要修改 state 就行了
如果等于0,說(shuō)明要釋放鎖,釋放鎖首先需要把獨(dú)占線程設(shè)置為null,再把state設(shè)置為0。
3 總結(jié)
Lock 鎖的實(shí)現(xiàn):
互斥性:需要一個(gè)狀態(tài)來(lái)判斷是否競(jìng)爭(zhēng)到鎖:state 并且需要用 volatile修飾,保證線程之間的可見性。
可重入性:Thread exclusiveOwnerThread 這個(gè)成員變量來(lái)記錄當(dāng)前獲得鎖的線程。
公平或非公平:默認(rèn)非公平,NonfairSync。
沒(méi)有競(jìng)爭(zhēng)到鎖的線程怎么辦?放到隊(duì)列中。
沒(méi)有競(jìng)爭(zhēng)到鎖的線程怎么釋放CPU?park:阻塞線程釋放CPU資源,這個(gè)操作在 acquireQueued(),阿粉沒(méi)沒(méi)有講這個(gè)。
最后來(lái)張流程圖: