1. What is ReetrantLock?
ReetrantLock is a reentrant Lock based on the Lock interface. When it comes to reentrant Lock, synchronized is also a reentrant Lock, but it is fundamentally different from synchronized. At the bottom, ReetrantLock is realized by AQS. Synchronized is implemented based on monitor.
ReetrantLock has two modes, one is fair lock mode, the other is unfair lock mode, in fact, the lock is obtained in order. Note: source code only analyzes unfair lock patterns.
2. How is AQS used in ReetrantLock?
ReetrantLock does not inherit from AQS directly. Instead, it inherits from AQS by internally defining an abstract Sync class, implementing some common lock methods such as tryRelease, and then inherits from Syn by defining fair and unfair classes. And then to achieve specific Lock method, in fact, here is the typical template method mode, AQS defined the Lock of the entire algorithm to perform the logical structure, specific Lock to subclass to achieve.
Below is its AQS implementation class in ReetrantLock.
//ReetrantLock global AQS subclass. // The Syn class is a static abstract class that inherits from the abstract synchronizer class AQS. A nonfairTryAcquire method that implements an unfair lock attempt, //3. Implements tryRelease try to release the lock method (ps: acquiring a lock and release the lock operation are the embodiment of the template method) the abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L; abstract void lock(); // Get the current thread first, and then get the global state variable with getState() (ps: This variable is a global traversal of AQS, it can determine the current lock is a thread into several times) / / and then by cas modify this state from 0 to 1, if successful, this thread for locks is successful, he set to the current holders of thread lock / / if you don't succeed, then to determine whether the current thread lock holders themselves, if it is reentrant, Change the number of states. Final Boolean nonfairTryAcquire(int acquires) {state final Thread current = thread.currentThread (); int c = getState(); If (c == 0) {// If 0, no thread has acquired the lock. 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; } // Try the lock release method, //1. Calculate the difference between the number of locks to be released and the current state, then determine if the current thread is the lock holder if not throwing exception //2. If the difference is equal to 0, the lock holder thread is cleared, making room for the next thread //3. Finally, change the state state. 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; }... } // The unfair Lock class, a subclass of Sync, implements the unfair Lock method Lock business logic //1. Set the current thread to lock holder thread //2. Call Acquire to get the lock, This method is a static final class NonfairSync extends Sync {private static Final Long serialVersionUID = 7316153563782823691L; Final void lock() {//cas changes state from 0-1 if successful, then acquire the lock, otherwise call acquire method for subsequent attempts to acquire the lock. if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // Try to acquire the lock method, which is called by AQS acquire method (ps: you can find template methods used here a lot). Protected Final Boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires); }} /** * Fair lock, fair lock will not try to modify state first, but acquire through acquire. */ static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } //1. Obtain the current thread and lock state, determine whether there is a waiting thread in the queue, if there is no and cas modification is successful, obtain the lock, change the holder thread //2. Determine if the current lock holder is you, 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; }}Copy the code
3. Lock execution process and source code analysis of ReetrantLock
Above is the general process of Lock, the following is part of the source code interpretation:
Public void lock() {sync.lock(); Final void lock() {if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } public final void acquire(int arg) {// If tryAcquire fails, try to acquire a lock. AcquireqQueued to queue to restart tryAcquire from the next node of the initial node. If successful, the current node is interrupted and the thread that acquires the lock executes. if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // The fourth step is to call NonfairSync tryAcquire protected Final Boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires); } // The fifth step is to call Sync's nonfairTryAcquire method, which is described in the previous part of the source code. Final Boolean nonfairTryAcquire(int acquires){} addWaiter private Node addWaiter(Node mode) { Node = new Node(thread.currentThread (), mode); // Add the current node to the end of the AQS queue and change it to the new node. if (pred ! = null) { node.prev = pred; If (compareAndSetTail(pred, node)) {pred. Next = node; return node; }} // Cas may fail to be changed to the tail node. Enq (node) initialization is performed after enQ fails. return node; } // spin, and check whether the tail node is null. If null, there is no waiting node and a node needs to be initialized as the head node, which acts like a sentinel. Private Node enq(final Node Node) {for (;;) { Node t = tail; If (t == null) {// Create a node as the head node, initialize if (compareAndSetHead(new node ())) tail = head; } else {// Reinsert the previous node. node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; }}}} // Step 7, invoke AQS acquireQueued, //1. After the node is added to the queue, the spin gets the previous node of the current node, //2. If the previous node of the current node is the head node, then there is only one thread waiting in the queue, call tryAcquire again to try to acquire the lock, and if successful, the queue node is empty and the head node is reset. Get failure, ready to hang, call the shouldParkAfterFailedAcquire before hanging, mainly dealing with some waiting for node has been awakened one need to be removed from the queue operations / / 4. 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; } / / processing awakened node, and suspends the current thread if (shouldParkAfterFailedAcquire (p, node) && parkAndCheckInterrupt ()) interrupted = true; } } finally { // if (failed) cancelAcquire(node); }} / / processing Node has awakened, private static Boolean shouldParkAfterFailedAcquire (Node Mr Pred, Node Node) {int ws = Mr Pred. WaitStatus; if (ws == Node.SIGNAL) return true; If (ws > 0) {// Find the node whose wait state is lower than while and insert the current node as the next node. do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; Private final Boolean parkAndCheckInterrupt() {locksupport.park (this); return Thread.interrupted(); }Copy the code
Simple summary: First, the thread attempts to acquire the lock. If it fails, a Node Node is created, which contains the current thread, and then added to the AQS bidirectional wait queue. Finally, another attempt is made to acquire the lock. However, the suspension is preceded by a call to remove the node from the wait queue that has been awakened by Signal and then added to the wait queue.
4. UnLock execution process and source code analysis of ReetrantLock
The figure above is the general process of unlocking, and here is a look at some specific source code interpretation:
Unlock public void unlock() {sync.release(1); Public final Boolean release(int arg) {// Call Syn tryRelease to try to obtain the lock. if (tryRelease(arg)) { Node h = head; If (h! = null && h.waitStatus ! = 0) unparkSuccessor(h); return true; } return false; } private void unparksucceeded (Node Node) {int ws = node.waitstatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); S = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t ! = null && t ! = node; t = t.prev) if (t.waitStatus <= 0) s = t; } // Wake up if (s! = null) LockSupport.unpark(s.thread); }Copy the code
If it is the current thread, change the state through CAS, and determine whether it is only re-entrant. If it is, the lock has been released successfully, and the subsequent nodes that can be woken up in the queue will be woken up. Otherwise, the thread of the lock holder will remain unchanged.
Others such as Condition analysis, to be continued….