Please please please please please please please please please please please please hurry! Some attention

ReentrantLock is a reentrant mutex, based on an AQS implementation, that has some of the same basic behavior and semantics as using synchronized methods and statements, but is more powerful.

The lock and unlock

All synchronization operations in ReentrantLock start with the Lock method. Lock Obtains a lock and performs a series of service operations. Then, use UNLOCK to release the lock.

private final ReentrantLock lock = new ReentrantLock();
public void sync(){
	lock.lock();
	try {
		// ... method body
	}
	finally {
		lock.unlock()
	}
}
Copy the code

lock

Is already in the implementation of the lock by calling the AQS AbstractQueuedSynchronizer# acquire method implementation.

Public final void acquire(int arg) {// Try to acquire the lockif(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

According to the template method pattern described earlier, tryAcquire for locks is implemented in ReentrantLock. The actual implementation method in non-fair lock is nonfairTryAcquire.

ReentrantLock#nonfairTryAcquire

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;
}   
Copy the code

In the logic of acquiring the lock, the first step is to try to acquire the lock in cas mode. If obtaining the lock fails, it indicates that the lock has been held by the thread.

Then determine whether the thread holding the lock is the current thread. If it is the current thread, increase the value of state by 1, and release the lock several times. This is the implementation of a reentrant lock.

The lock fails if the thread holding the lock is not the current thread, returns false. Lock the fall will call AbstractQueuedSynchronizer# acquireQueued (addWaiter (Node. EXCLUSIVE), arg).

The addWaiter method is first called to enqueue the thread.

private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);
	Node pred = tail;
	if(pred ! = null) { node.prev = pred;if (compareAndSetTail(pred, node)) {
			pred.next = node;
			return node;
		}
	}
	enq(node);
	return node;
}
Copy the code

Mode specifies the mode in which a Node is added to the queue. In this case, node. EXCLUSIVE is passed. First wrap the current thread as a Node node. Then determine whether the last node of the waiting queue is empty. If not, the current node is connected to the last node of the waiting queue through CAS. If tail is empty, the enq method is executed.

AbstractQueuedSynchronizer#enq

private Node enq(final Node node) {
	for (;;) {
		Node t = tail;
		if (t == null) {
			// Must initialize
			if (compareAndSetHead(new Node()))
			                tail = head;
		} else {
			node.prev = t;
			if (compareAndSetTail(t, node)) {
				t.next = node;
				returnt; }}}}Copy the code

The enq method passes for(;;) Set the node node to the end of the queue in an infinite loop (head and tail point to the current node when the queue is empty).

In summary, the addWaiter method is used to add the node that fails to compete for the lock to the end of the queue.

The nodes in the waiting queue are not doing nothing, they are constantly trying to acquire the lock, and the logic is implemented in the acquireQueued.

AbstractQueuedSynchronizer#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); }}Copy the code

You can see that this method also uses for(;). An infinite loop to try to get the lock. First determine whether the current node is the next node to the head node. If so, call tryAcquire again to try to acquire the lock. Of course, this process does not have to be continuous, so CPU switching in multi-threaded competition can be very costly.

ShouldParkAfterFailedAcquire will determine whether the current node for block, block after only when unpark nodes will continue if the battle for the lock.

private static Boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	int ws = pred.waitStatus;
	if (ws == Node.SIGNAL)
	        return true;
	if (ws > 0) {
		do {
			node.prev = pred = pred.prev;
		}
		while (pred.waitStatus > 0);
		pred.next = node;
	} else {
		compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	}
	return false;
}
Copy the code

Whether a node needs to be blocked is determined by the state of its successors.

If the status of the successor node is singal, it indicates that the successor node is still waiting and the current node must continue to be blocked. Returns true.

If the predecessor node is greater than 0, it indicates that the predecessor node is in the cancelled state. The node in the cancelled state does not compete for the lock and is skipped directly. Returns false.

If there is no blocking when the preceding node is in other state (0,PROPAGATE), it means that the current node needs to try again to acquire the lock. Returns false.

ShouldParkAfterFailedAcquire method if returns true, said to the current node block, blocking method for parkAndCheckInterrupt.

AbstractQueuedSynchronizer#parkAndCheckInterrupt

private final Boolean parkAndCheckInterrupt() {
	LockSupport.park(this);
	return Thread.interrupted();
}
Copy the code

The blocked node is blocked through LockSupport. The blocked node does not compete for the lock (does not cycle to obtain the lock) and can continue to compete for the lock only after being unpark.

The unlock method is used to unlock a blocked node.

unlock

Is already in the realization of the unlock by calling the AQS AbstractQueuedSynchronizer# release method implementation.

public final Boolean release(int arg) {
	if (tryRelease(arg)) {
		Node h = head;
		if(h ! = null && h.waitStatus ! = 0) unparkSuccessor(h);return true;
	}
	return false;
}
Copy the code

Release calls the tryRelease method, which is implemented in ReentrantLock.

ReentrantLock#tryRelease

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;
}
Copy the code

The tryRelease method has very simple logic. First subtract releases a lock (typically 1) and releases the lock successfully with state=0, which then releases the lock to the waiting node. If the state! If =0, the lock is a reentrant lock and needs to be released multiple times.

When the lock is released successfully (state=0), the subsequent nodes of the head node are unpark.

AbstractQueuedSynchronizer#unparkSuccessor

int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node 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; } if (s ! = null) LockSupport.unpark(s.thread); }Copy the code

Unparksucceeded is known to apply to states of obstruction that meet the succeeding nodes. The entire logic of the method is to find the successor of the incoming node and wake it up (excluding nodes whose status is Cancel, or waitStatus > 0).

Fair locks and unfair locks

The constructor of ReentrantLock accepts an optional fair argument. When set to true, locks tend to be allocated to the thread with the longest waiting time in contention between multiple threads.

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code

In an environment where multiple locks compete for unified resources, AQS maintains a wait queue, and threads that fail to acquire locks will be attached to this queue. If a fair lock is used, the resource is fetched from the head of the queue.

And according to the code in the fair lock and the difference between the implementation of unfair lock only lies in the fair lock more than a detection method.

Fair lock

protected final boolean tryAcquire(int acquires) {
    //...
    if (c == 0) {
        if(! hasQueuedPredecessors() //! Hasqueuedmonopoly () {compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);
            return true;
        }
    }
    //...
    return false;
}
Copy the code

hasQueuedPredecessors()

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    returnh ! = t && ((s = h.next) == null || s.thread ! = Thread.currentThread()); }Copy the code

The logic of the method is simple: if there are more nodes in the queue and the first node is not the node of the current thread, return true to indicate that there are more nodes waiting for longer. You can obtain resources only after these nodes obtain resources.