大家都用過ReentrantLock,但是大家對內部實現是否足夠了解呢,下面我就簡單說一下其中的實現原理。
ReentrantLock是可重入鎖,也就是同一個線程可以多次獲取鎖,每獲取一次就會進行一次計數,解鎖的時候就會遞減這個計數,直到計數變為0。
它有兩種實現,一種是公平鎖,一種是非公平鎖,那么默認是什么鎖呢?看完如下代碼想必你也知道了。
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); }
它的內部結構的實現是如何的呢? 首先NonFairSync類是靜態內部類,它繼承了Sync。
/** * Sync object for non-fair locks */ static final class NonfairSync extends Sync
Sync繼承了AbstractQueuedSynchronizer,簡稱AQS。同時Sync里邊實現了tryRelease方法,因為公平鎖和非公平鎖都可以用這個方法釋放鎖。
/** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. */ abstract static class Sync extends AbstractQueuedSynchronizer
繼續看非公平鎖的lock方法,采用CAS進行當前狀態的設置state=0,表示沒有線程占用,state=1表示已經有現成占用了,設置成功了,將當前線程設置為線程擁有者,並且是排他的。如果有現成占用了,那么需要進入acquire(1),需要獲取一個鎖。
/** * 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); }
acquire方法首先進行tryAcquire,嘗試獲取鎖,即調用nonfairTryAcquire,判斷當前鎖是否state=0, 則沒有現成占用,則進行設置。如果被占用了判斷該線程是否是當前線程占用的,如果是的話,那么可以進行重入,即當前可以獲取鎖,計數器進行加1。否則的話返回失敗。返回失敗后執行addWaiter方法,也就是添加到等待的隊列。Node是一個雙向列表,也就是把需要等待的線程放到放到Node,並且鏈接起來。
/** * Acquires in exclusive mode, ignoring interrupts. Implemented * by invoking at least once {@link #tryAcquire}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquire} until success. This method can be used * to implement method {@link Lock#lock}. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquire} but is otherwise uninterpreted and * can represent anything you like. */ public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ 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; } /** * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node */ private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
接下來看一下Node的大致內容,有兩個指針,一個是prev,一個是next,還保存着當前的線程。同時里邊還有一個共享鎖和獨占鎖,SHARED和EXCLUSIVE。ReentrantLock采用的就是獨占鎖。Semaphore,CountDownLatch等采用的是共享鎖,即有多個線程可以同時獲取鎖。
static final class Node { /** Marker to indicate a node is waiting in shared mode */ static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ static final Node EXCLUSIVE = null; /** * Link to predecessor node that current node/thread relies on * for checking waitStatus. Assigned during enqueuing, and nulled * out (for sake of GC) only upon dequeuing. Also, upon * cancellation of a predecessor, we short-circuit while * finding a non-cancelled one, which will always exist * because the head node is never cancelled: A node becomes * head only as a result of successful acquire. A * cancelled thread never succeeds in acquiring, and a thread only * cancels itself, not any other node. */ volatile Node prev; /** * Link to the successor node that the current node/thread * unparks upon release. Assigned during enqueuing, adjusted * when bypassing cancelled predecessors, and nulled out (for * sake of GC) when dequeued. The enq operation does not * assign next field of a predecessor until after attachment, * so seeing a null next field does not necessarily mean that * node is at end of queue. However, if a next field appears * to be null, we can scan prev's from the tail to * double-check. The next field of cancelled nodes is set to * point to the node itself instead of null, to make life * easier for isOnSyncQueue. */ volatile Node next; /** * The thread that enqueued this node. Initialized on * construction and nulled out after use. */ volatile Thread thread; /** * Link to next node waiting on condition, or the special * value SHARED. Because condition queues are accessed only * when holding in exclusive mode, we just need a simple * linked queue to hold nodes while they are waiting on * conditions. They are then transferred to the queue to * re-acquire. And because conditions can only be exclusive, * we save a field by using special value to indicate shared * mode. */ Node nextWaiter;
大致的思路我們看了一下,總體的流程圖我畫了一下。ReentrankLock內核采用的是AQS實現的,AQS里邊采用的是雙向鏈表,即如果當前線程未獲取到鎖將會加入到鏈表中。
那么公平鎖和非公平鎖的實現的不同點在哪里呢?公平鎖和非公平鎖就差在 !hasQueuedPredecessors() ,也就是前邊沒有排隊者的話,我就可以獲取鎖了。
/** * 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; }
如果當前線程之前還有線程等待就會返回true,如果當前節點是頭結點,或者當前隊列為空就會返回false。非公平鎖沒有這句話的判斷,所以直接去競爭鎖。
/** * Queries whether any threads have been waiting to acquire longer * than the current thread. * * <p>An invocation of this method is equivalent to (but may be * more efficient than): * <pre> {@code * getFirstQueuedThread() != Thread.currentThread() && * hasQueuedThreads()}</pre> * * <p>Note that because cancellations due to interrupts and * timeouts may occur at any time, a {@code true} return does not * guarantee that some other thread will acquire before the current * thread. Likewise, it is possible for another thread to win a * race to enqueue after this method has returned {@code false}, * due to the queue being empty. * * <p>This method is designed to be used by a fair synchronizer to * avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>. * Such a synchronizer's {@link #tryAcquire} method should return * {@code false}, and its {@link #tryAcquireShared} method should * return a negative value, if this method returns {@code true} * (unless this is a reentrant acquire). For example, the {@code * tryAcquire} method for a fair, reentrant, exclusive mode * synchronizer might look like this: * * <pre> {@code * protected boolean tryAcquire(int arg) { * if (isHeldExclusively()) { * // A reentrant acquire; increment hold count * return true; * } else if (hasQueuedPredecessors()) { * return false; * } else { * // try to acquire normally * } * }}</pre> * * @return {@code true} if there is a queued thread preceding the * current thread, and {@code false} if the current thread * is at the head of the queue or the queue is empty * @since 1.7 */ public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
這個就是ReentrantLock的基本原理,接下來咱們繼續看看與之一塊使用的Condition。Condition是一個接口,它的實現類是ConditionObject。調用await的時候也會將當前線程的一些信息加入到隊列當中。ConditionObject中有一個firstWaiter和LastWaiter分別指向的了等待隊列的頭和尾。
當調用Condition的signal方法是,則會將第一個Node轉換到同步隊列,如下圖所示。
好了,總結一下:
1. ReentrankLock默認是非公平鎖。
2.ReentrankLock的內部實現采用的AQS的雙向鏈表實現。獲取鎖的線程會被封裝成Node里邊,供后續使用。
3.公平鎖采用判斷當前Node是不是頭結點,如果是的話就獲取鎖並做業務處理,不是頭結點的不能獲取所。
4.非公平鎖沒有判斷當前結點,采用CAS,誰第一個拿到了state=0,則視為獲取鎖。
5.Condition的await和notify也采用類似的機制,當執行await是,會將當前線程信息的相關信息放入到Node的列表,記錄firstWaiter和lastWaiter指向的信息。
希望對大家有所幫助,如果有問題的請及時指出。