ReentrantLock is a reentrant mutex lock, which means that it can only be held by one thread at the same point in time; reentrant means that the ReentrantLock lock can be acquired multiple times by the same thread.
ReentrantLock has a distinction between fair and unfair. A FIFO waiting queue is used to manage the acquisition of the lock by all threads.
Under the “fair lock” mechanism, threads queue up to acquire locks in sequence;
The “unfair lock” will acquire the lock regardless of whether it is at the beginning of the queue when the lock is available.
1.Construction method:
//The empty parameter constructor of ReentrantLock generates an unfair lock by default public ReentrantLock() {<!-- --> sync = new NonfairSync(); } //Generate fair lock when the parameter passed in the constructor is true public ReentrantLock(boolean fair) {<!-- --> sync = fair ? new FairSync() : new NonfairSync(); }
ReentrantLock is implemented by AQS, and the sync object in the locking code is inherited from the abstract queue synchronizer of AQS (AbstractQueuedSynchronizer).
2. Lock
2.1 Method Lock()
//ReentrantLock locking method public void lock() {<!-- --> sync.lock(); } //Lock implementation of fair lock final void lock() {<!-- --> acquire(1); } //Lock implementation of unfair lock final void lock() {<!-- --> //Use CAS for comparison and exchange. In AQS, the State value represents the status of the lock. 0 represents that the current lock has been released, and 1 represents that the lock has been acquired. //compareAndSetState changes the lock status from 0 to 1. If this method is successful, it means that the current thread has obtained the lock. if (compareAndSetState(0, 1)) //After getting the lock, set the lock holder to be the current thread setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
2.1.1 acquire method
acquire(1) is called in both fair lock and unfair lock locking methods.
public final void acquire(int arg) {<!-- --> //Try to acquire the lock if (!tryAcquire(arg) & amp; & amp; //Attempt to acquire lock failed and join AQS waiting queue acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //Interrupt thread selfInterrupt(); }
2.1.1.1 tryAcquire method of fair lock
//tryAcquire of fair lock protected final boolean tryAcquire(int acquires) {<!-- --> //Get the current thread final Thread current = Thread.currentThread(); //Get the current lock status. State 0 represents that the current lock has been released, 1 represents that the lock has been acquired once. int c = getState(); //When the lock status is released if (c == 0) {<!-- --> //Get whether the current thread is waiting for the head of the queue or the waiting queue is empty if (!hasQueuedPredecessors() & amp; & amp; //Is the lock in the released state? If so, acquire the lock. compareAndSetState(0, acquires)) {<!-- --> setExclusiveOwnerThread(current); return true; } } //When the current thread is the thread holding the lock, reentrancy logic is triggered else if (current == getExclusiveOwnerThread()) {<!-- --> //Add 1 to the number of reentries int nextc = c + acquires; //When nextc is a negative number, it means that the number of reentries exceeds the maximum value of int if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //Return failed to acquire lock return false; } //True if there is a queued thread in front of the current thread; false if the current thread is at the head of the queue or the queue is empty public final boolean hasQueuedPredecessors() {<!-- --> Node t = tail; //The tail node of the waiting queue in AQS Node h = head;//The head node of the waiting queue in AQS Node s; //The head is equal to the tail, indicating that the queue is empty return h != t & amp; & amp; //The head node of the waiting queue in AQS is a virtual empty node //h.next is equal to empty, which means the queue is empty ((s = h.next) == null || //Is the current node the first node? s.thread != Thread.currentThread()); }
2.1.1.2 tryAcquire method of unfair lock
The basic logic of the tryAcquire method of unfair lock is similar to that of fair lock, except that it lacks a judgment on whether the waiting queue is empty or whether it is waiting for the head of the queue before acquiring the lock.
protected final boolean tryAcquire(int acquires) {<!-- --> return nonfairTryAcquire(acquires); } 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; }
2.1.1.3 Join the waiting queue after failing to acquire the lock
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; //When the tail node is not empty if (pred != null) {<!-- --> //The current node's previous pointer points to the tail node node.prev = pred; //Use cas to point the tail node from pred to the current node if (compareAndSetTail(pred, node)) {<!-- --> pred.next = node; return node; } } //If the tail node is empty, the waiting queue has not completed initialization and the enq method is executed. enq(node); return node; } private Node enq(final Node node) {<!-- --> for (;;) {<!-- --> //In the case of multi-threading, the tail node t is not necessarily empty. Node t = tail; //The tail node is empty and the waiting queue has not completed initialization. if (t == null) {<!-- --> // Must initialize //Set the empty node as the head node if (compareAndSetHead(new Node())) //Set the head node and tail node to point to the same node tail = head; } else {<!-- --> //Wait for the queue initialization to complete, set the current node as the new tail node and return node.prev = t; if (compareAndSetTail(t, node)) {<!-- --> t.next = node; return t; } } } } final boolean acquireQueued(final Node node, int arg) {<!-- --> boolean failed = true; try {<!-- --> boolean interrupted = false; //infinite loop for (;;) {<!-- --> //Get the previous node of the current node final Node p = node.predecessor(); //The previous node is the head node, then try to acquire the lock if (p == head & amp; & amp; tryAcquire(arg)) {<!-- --> //Successfully acquire the lock, set the current node to an empty node, and set it to the head node setHead(node); p.next = null; // help GC failed = false; //Return without interruption return interrupted; } // Based on the change of the previous node, determine whether the current node can suspend the thread. If it can, return true. if (shouldParkAfterFailedAcquire(p, node) & amp; & amp; parkAndCheckInterrupt()) interrupted = true; } } finally {<!-- --> if (failed) cancelAcquire(node); } } private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {<!-- --> int ws = pred.waitStatus; //The waitStatus of the previous node is -1, indicating that the current node can suspend threads if (ws == Node.SIGNAL) return true; if (ws > 0) {<!-- --> //The waitStatus of the previous node is 1, and the previous node has been cancelled. We need to look forward for nodes whose waitStatus is not 1. do {<!-- --> node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else {<!-- --> //The status of the previous node is not 1 or -1, which means the node status is normal. Change the status of the previous node to -1 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } // Here, based on the park method of the Unsafe class, the current thread is suspended. private final boolean parkAndCheckInterrupt() {<!-- --> LockSupport.park(this); return Thread.interrupted(); }
2.2 Locking method tryLock
2.2.1 tryLock()
tryLock method, both fair lock and unfair lock use the same implementation, which is the implementation of unfair lock
public boolean tryLock() {<!-- --> return sync.nonfairTryAcquire(1); } //The logic is the same as the tryAcquire method implementation of the Lock method of the unfair lock 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; }
2.2.2 tryLock(timeout,unit)
tryAcquire(1) in tryLock(timeout,unit) tries to obtain the fair and unfair implementation of lock points
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {<!-- --> return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {<!-- --> //Whether the thread is interrupted if (Thread.interrupted()) throw new InterruptedException(); //Try to acquire the lock return tryAcquire(arg) || //Acquisition of lock failed, doAcquireNanos(arg, nanosTimeout); } private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {<!-- --> //If the waiting time is less than 0, give up acquiring the lock if (nanosTimeout <= 0L) return false; //calculating time final long deadline = System.nanoTime() + nanosTimeout; //Join the waiting queue final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try {<!-- --> //infinite loop for (;;) {<!-- --> //Get the previous node of the current node final Node p = node.predecessor(); if (p == head & amp; & amp; tryAcquire(arg)) {<!-- --> setHead(node); p.next = null; // help GC failed = false; return true; } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; //Hang after failure to obtain the lock if (shouldParkAfterFailedAcquire(p, node) & amp; & amp; nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally {<!-- --> if (failed) cancelAcquire(node); } }
tryLock(timeout,unit) tries to acquire the lock and gives up if it does not acquire it within the specified time.
2.3 Locking method lockInterruptibly()
lockInterruptibly is no different from tryLock(timeout,unit), except that it lacks the time parameter. Like the lock method, it will not give up after failing to acquire the lock, and will wait forever.
3. Release the lock
public void unlock() {<!-- --> sync.release(1); } public final boolean release(int arg) {<!-- --> if (tryRelease(arg)) {<!-- --> //Release lock successfully Node h = head; //The head node is not empty, and the status of the head node is not 0, indicating that there are still nodes hanging in the waiting queue, and the wake-up method is called. if (h != null & amp; & amp; h.waitStatus != 0) // unparkSuccessor(h); return true; } return false; } protected final boolean tryRelease(int releases) {<!-- --> //Get the lock status -1 int c = getState() - releases; //If the current thread does not hold the lock, an error will be reported if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //When c equals 0, it means the lock is released cleanly if (c == 0) {<!-- --> //Release lock successfully free = true; //Set that no thread currently acquires the lock setExclusiveOwnerThread(null); } setState(c); return free; } private void unparkSuccessor(Node node) {<!-- --> int ws = node.waitStatus; if (ws < 0) //Change the head node status to 0 compareAndSetWaitStatus(node, ws, 0); Node s = node.next; // If the subsequent node is null or the status of the subsequent node is 1, it means the node has been cancelled. if (s == null || s.waitStatus > 0) {<!-- --> s = null; //Traverse backward to obtain normal nodes with status less than 0 for (Node t = tail; t != null & amp; & amp; t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) //Wake up the node LockSupport.unpark(s.thread); }