互聯網JAVA面試常問問題(六)
- 2019 年 10 月 6 日
- 筆記
前幾篇文章,都介紹了JAVA面試中鎖相關的知識。其實關於JAVA鎖/多執行緒,你還需要知道了解關於ReentrantLock的知識,本文將從源碼入手,介紹可重入鎖的相關知識。
ReentrantLock
先來看看ReentrantLock的源碼,部分程式碼塊用省略號代替,後面會詳細展開介紹:
public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ private final Sync sync; 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(); /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { ....... } 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; } protected final boolean isHeldExclusively() { // While we must in general read state before owner, // we don't need to do so to check if current thread is owner return getExclusiveOwnerThread() == Thread.currentThread(); } ........ } /** * Sync object for non-fair locks */ static final class NonfairSync extends Sync { ....... } /** * Sync object for fair locks */ static final class FairSync extends Sync { ........... } .......... }
可以看出可重入鎖的源碼中,其實實現了公平鎖和非公平鎖。
ReentrantLock中有一個靜態內部抽象類Sync,然後有NonfairSync和FairSync兩個靜態類繼承了Sync。
其中Sync繼承了AQS(AbstractQueuedSynchronizer),接下來的文章中會介紹詳細AQS。
我們在使用可重入鎖的時候,需要明顯的加鎖和釋放鎖的過程。一般在finally程式碼中實現鎖釋放的過程。
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while(條件判斷表達式) { condition.wait(); } // 處理邏輯 } finally { lock.unlock(); }
非公平鎖的實現
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); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
可以看出,非公平鎖在執行lock的時候,會用CAS來嘗試將鎖狀態改成1,如果修改成功,則直接獲取鎖,用setExclusiveOwnerThread方法講當前執行緒設置為自己。如果沒有修改成功,則會執行acquire方法來嘗試獲取鎖。其中,nonfairTryAcquire實現如下:
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; }
可以看出這個方法,其實也是在用CAS嘗試將執行緒狀態置為1。其實也是一個多次嘗試獲取的過程。
所以,對於非公平鎖,當一執行緒空閑時候,其他所有等待執行緒擁有相同的優先順序,誰先爭搶到資源即可以獲取到鎖。
公平鎖的實現
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { 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; } }
主要看當公平鎖執行lock方法的時候,會調用 acquire方法, acquire方法首先嘗試獲取鎖並且嘗試將當前執行緒加入到一個隊列中,所以公平鎖其實是維護了一個隊列,誰等待的時間最長,當執行緒空閑時候,就會最先獲取資源:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
如果想了解acquireQueued的話,可以參照一下程式碼:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
綜上,可重入鎖利用CAS原理實現了公平鎖和非公平鎖,為什麼叫做可重入鎖呢?其實在程式碼方法tryAcquire中可以看到,執行緒可以重複獲取已經持有的鎖。
if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; }
今天小強從源碼的角度分析了ReentrantLock,希望對正在學習JAVA的你有所幫助。