ReentrantLock is an explicit lock for Java implementations starting with JDK5, which is a ReentrantLock that relies on the AQS implementation. It uses the state variable of AQS as the number of reentrants of the lock. The state increases by 1 for each lock() and decreases by 1 for each unlock(). When the state decreases to 0, the lock is released.

The AQS, after the locks are released, call the unparksucceeded () to wake up the successors in the queue to compete for the locks. The thread that failed the lock race AQS would create a Node Node bound to the thread, join the queue and Park the thread.

The details of thread synchronization are already covered in the AQS class, and ReentrantLock can quickly implement a Java synchronization lock with very little code.

Note: ReentrantLock is based on AQS to implement, before this must first understand the source of AQS, THE author of another article:AQS source guide”

attribute

The properties of ReentrantLock are simple. The internal class Sync is a synchronizer based on the AQS implementation, with two implementations: FairSync and NonfairSync.

// AQS based synchronizer implementation, there are two kinds of fair and unfair implementation.
private final Sync sync;
Copy the code

The constructor

ReentrantLock is more efficient by default by using non-fair locks, but in case of thread hunger, it can also be implemented by specifying fair locks via the constructor.

/* Default use unfair lock */
public ReentrantLock(a) {
	sync = new NonfairSync();
}

/* fair Specifies a fair lock/an unfair lock */
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code

Core method

lock

Sync.lock () is called for locking. The logic for fair and unfair locking is different:

/* Compete for locks, calling tryAcquire() or nonfairTryAcquire() depending on the type of lock. * /
public void lock(a) {
	sync.lock();
}
Copy the code

Unfair lock: Grab the lock directly, regardless of whether there are threads waiting in the queue.

/* Lock is not an equitable lock, regardless of whether there are threads waiting in the queue. * /
final void lock(a) {
	// the CAS method changes the state. If the change is successful, no other thread holds the lock, and the current thread is set as the exclusive lock holder
	if (compareAndSetState(0.1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		// the CAS fails, indicating that another thread already holds the lock
		acquire(1);
}
Copy the code

Fair lock: call AQS acquire() :

/ / fair lock
final void lock(a) {
	acquire(1);
}
Copy the code

Acquire() is a template method in AQS that first calls tryAcquire() to try to acquire the lock, if it fails to create the node, enqueue and Park the thread:

1. TryAcquire (): Try again to acquire the lock. 2. AddWaiter (): If not, add a Node to the end of the queue. AcquireQueued (): Go to the queue. * /
public final void acquire(int arg) {
	if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// Whether a self-interrupt is needed to make up for the interrupt that occurred during the thread wait.
		selfInterrupt();
}
Copy the code

TryAcquire () is very polite, even if the current state is not locked, to determine whether there is a thread in the queue is waiting, as long as there is a thread in the queue, give up the lock, join the queue and suspend:

// Fair lock - Try to get the lock
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		/* If there are threads in the queue that are already waiting, even if the current state is unlocked. If another thread is waiting, let the other thread acquire the lock first and join the queue and suspend. If there are no threads in the queue, a CAS race is attempted. * /
		if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true; }}// The current thread is the thread that holds the lock
	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

If a thread is not on the queue, then the CAS lock is not on the queue.

/* Unfair lock attempts to acquire the lock. If the competition is successful, it is returned directly. If the competition fails, it is submitted to AQS for processing. AQS creates a Node bound to the current thread, enqueues it, and parks it. * /
final boolean nonfairTryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		// state==0, indicating that another thread has released the lock.
		if (compareAndSetState(0, acquires)) {
			// Set the current thread to be the thread holding the lock.
			setExclusiveOwnerThread(current);
			return true; }}Reentrant, state++, if the current thread is the thread holding the lock.
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0) // The lock cannot be reentrant indefinitely. If the number of reentrants exceeds the int maximum, the lock will overflow.
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	// If no other thread releases the lock, the current thread is not the thread that holds the lock.
	return false;
}
Copy the code

If a lock grab fails, AQS will create a Node bound to the current thread, insert the Node into the tail of the queue, and call locksupport.park () to suspend the current thread. AQS will not wake up the threads in the queue until the thread holding the lock releases the lock. All of these operations are completed in AQS, so the amount of ReentrantLock code is not much, to understand the implementation details of AQS, you can see the author of another article: “AQS source guide”.

tryLock

A thread that calls the lock() method will always be dead if it can’t get the lock, which is easy to deadlock if the code is written incorrectly. ReentrantLock provides a more flexible approach: tryLock(). It can attempt to acquire the lock without blocking and return true on acquisition or false on failure. You can also try to obtain the lock within a given timeout period. If the lock cannot be obtained, the lock will exit due to timeout and no longer compete for the lock, avoiding deadlock.

Call nonfairTryAcquire() directly to determine whether the state of AQS is equal to 0. If so, it indicates that there is no lock. Change the state from 0 to 1 through CAS, and the lock is captured if the change is successful. Otherwise return false.

/* Try to compete for locks in an unfair way. This will only succeed if there is no lock, no spin retry. * /
public boolean tryLock(a) {
	return sync.nonfairTryAcquire(1);
}
Copy the code

TryLock (long timeout, TimeUnit unit) Specifies a timeout period during which to attempt to acquire the lock. The difference with lock() is that the thread is not suspended indefinitely. Instead, it calls locksupport.parknanos () to suspend for a specified time. Wake up automatically after timeout, return false if no lock is acquired and exit contention.

/* Attempts to acquire the lock within the given timeout period. The process is similar to lock() : 1. TryAcquire () will be called to try to acquire it. If it cannot obtain it, a Node will be created to join the queue. 2. Call locksupport.parknanos () to suspend the current thread for a specified time. 3. If no lock is obtained after the timeout, false is returned and AQS removes the corresponding Node. * /
public boolean tryLock(long timeout, TimeUnit unit)
		throws InterruptedException {
	return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
Copy the code

TryAcquireNanos (), again a template method in AQS, will first call tryAcquire() to attempt to acquire the lock, and if not, will call doAcquireNanos() to enqueue and suspend the thread.

/** * gets */ in exclusive mode within a timeout period
private boolean doAcquireNanos(int arg, long nanosTimeout)
		throws InterruptedException {
	if (nanosTimeout <= 0L)
		return false;
	// Calculate the expiration time
	final long deadline = System.nanoTime() + nanosTimeout;
	// Create a Node and join the queue
	final Node node = addWaiter(Node.EXCLUSIVE);
	boolean failed = true;
	try {
		for (;;) {
			// Get the precursor node of the current node
			final Node p = node.predecessor();
			// If the front node is the head node, then it is eligible to compete for the lock.
			if (p == head && tryAcquire(arg)) {
				// The lock was snatched successfully
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return true;
			}
			// Calculates how long the thread needs to be suspended
			nanosTimeout = deadline - System.nanoTime();
			if (nanosTimeout <= 0L)
				// There is no need to suspend the lock
				return false;
			// Determine whether to suspend after lock snatching fails by setting waitStatus on the precursor node to SIGNAL
			if (shouldParkAfterFailedAcquire(p, node) &&
					nanosTimeout > spinForTimeoutThreshold)
				// Suspend the specified time
				LockSupport.parkNanos(this, nanosTimeout);
			if (Thread.interrupted())
				// This procedure responds to interrupts and throws exceptions if interrupts occur
				throw newInterruptedException(); }}finally {
		if (failed)
			// If the lock still fails, cancel the contention nodecancelAcquire(node); }}Copy the code

lockInterruptibly

Lock () does not respond to interrupts, and is ignored even if interrupt() is called on the thread during blocking. AQS only fill a self-interrupt after competing for the lock. The lockInterruptibly(), on the other hand, responds to interrupts and wakes the current thread up from the Park when another thread calls interrupt(), throwing InterruptedException.

/* Lock () does not respond to interrupts, even if the thread calls interrupt(), and AQS only completes a self-interrupt after competing for the lock. LockInterruptibly () responds to interrupts and wakes the current thread up from the Park when other threads call interrupt() on it, throwing InterruptedException. * /
public void lockInterruptibly(a) throws InterruptedException {
	sync.acquireInterruptibly(1);
}
Copy the code

AcquireInterruptibly(), again a template method of AQS, first calls tryAcquire() to attempt to acquire the lock, and then doAcquireInterruptibly() to obtain the lock if it fails. The logic is similar to acquireQueued(), except that thread interrupts are treated differently. AcquireQueued () in the face of a thread interrupt simply marks a self-interrupt until the lock is acquired. DoAcquireInterruptibly () throws InterruptedException to exit the race.

/** * Exclusive mode, interruptible access to resources */
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw newInterruptedException(); }}finally {
        if(failed) cancelAcquire(node); }}Copy the code

unlock

Unlock, only lock hold thread can call, or you will be thrown IllegalMonitorStateException anomalies. A ReentrantLock is a ReentrantLock. Unlock () is a decrement of state. The lock is released only when the state drops to zero.

/* Unlock, if the current thread is the thread that holds the lock, the state decreases by 1, when it decreases to 0, the lock is released and the node in the queue is woken up. If the current thread is not the thread holding the lock, it is illegal to unlock, flip IllegalMonitorStateException anomalies. * /
public void unlock(a) {
	sync.release(1);
}
Copy the code

Release() is the template method of AQS that calls the subclass’s tryRelease(), returning true to indicate that the lock was really released, and AQS calls the unparksucceeded () to wake up the threads in the waiting queue.

/ / releases the lock
public final boolean release(int arg) {
	/* Call subclass tryRelease(), returning true for successful lock release. For ReentrantLock, state reduced to 0 means the lock needs to be released. * /
	if (tryRelease(arg)) {
		/* head represents the node holding the lock. If head's waitStatus! =0, indicating that a successor node is waiting to be woken up. Remember when a thread is enqueued, if it wants to suspend, it must change the waitStatus of its precursor to -1?? If a node is enqueued without changing the waitStatus of its precursor, it cannot be woken up. * /
		Node h = head;
		if(h ! =null&& h.waitStatus ! =0)
			// Wake up the subsequent nodes after releasing the lock
			unparkSuccessor(h);
		return true;
	}
	return false;
}
Copy the code

ReentrantLock: state==0;

/* Attempt to release the lock, return true to indicate that the lock was successfully released, and AQS will wake up subsequent nodes. Only the thread holding the lock can release it, so there is no concurrency problem. * /
protected final boolean tryRelease(int releases) {
	int c = getState() - releases;
	if(Thread.currentThread() ! = getExclusiveOwnerThread())// Only the thread holding the lock can unlock it
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {
		// state==0, release the lock, the thread holding the lock is null
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}
Copy the code

Other methods

/* Gets the number of reentrant times for the current thread lock, and returns 0 */ for non-holding threads
public int getHoldCount(a) {
	return sync.getHoldCount();
}

/* Whether the current thread is the thread holding the lock */
public boolean isHeldByCurrentThread(a) {
	return sync.isHeldExclusively();
}

/* Lock state :state! Why is state>0? State overflow occurs. * /
public boolean isLocked(a) {
	return sync.isLocked();
}

/* Is a fair lock */
public final boolean isFair(a) {
	return sync instanceof FairSync;
}

/* Returns the thread that holds the lock, or null */ if there is no lock
protected Thread getOwner(a) {
	return sync.getOwner();
}

/* Is there a thread waiting for the lock? Judge by :AQS queue head node! = last node */
public final boolean hasQueuedThreads(a) {
	return sync.hasQueuedThreads();
}

/* Is the thread waiting to acquire the lock? Traverse the AQS queue to determine whether Thread objects are equal. * /
public final boolean hasQueuedThread(Thread thread) {
	return sync.isQueued(thread);
}

/* Returns an estimate of the number of waiting threads. The nodes whose threads are not null in the AQS queue are traversed, and the sum is calculated. Inaccurate because the node data may change during traversal. * /
public final int getQueueLength(a) {
	return sync.getQueueLength();
}

/* Returns the set of threads that may be waiting for the lock. Returns by iterating over nodes in the AQS queue whose threads are not null and adding threads to the ArrayList. Inaccurate because the node data may change during traversal. * /
protected Collection<Thread> getQueuedThreads(a) {
	return sync.getQueuedThreads();
}

/* Are there waiting threads in the conditional queue? Traverse ConditionObject Node in the queue, to determine whether a state of Node to Node. The CONDITION. * /
public boolean hasWaiters(Condition condition) {
	if (condition == null)
		throw new NullPointerException();
	if(! (conditioninstanceof AbstractQueuedSynchronizer.ConditionObject))
		throw new IllegalArgumentException("not owner");
	return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

/ * may return conditions the queue waiting for the number of threads traversal ConditionObject Node in the queue, to determine whether a state of Node to Node. The CONDITION, the accumulative calculation. * /
public int getWaitQueueLength(Condition condition) {
	if (condition == null)
		throw new NullPointerException();
	if(! (conditioninstanceof AbstractQueuedSynchronizer.ConditionObject))
		throw new IllegalArgumentException("not owner");
	return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}

/* Returns the set of possible waiting lines in the conditional queue. Traverse ConditionObject Node in the queue, to determine whether a state of Node to Node. The CONDITION, add a thread to the ArrayList in return. * /
protected Collection<Thread> getWaitingThreads(Condition condition) {
	if (condition == null)
		throw new NullPointerException();
	if(! (conditioninstanceof AbstractQueuedSynchronizer.ConditionObject))
		throw new IllegalArgumentException("not owner");
	return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}
Copy the code

conclusion

AQS design is really excellent, template method pattern, most of the logic is done at the bottom, subclasses only need to write simple code to implement a good synchronization tool class.

ReentrantLock is a synchronous lock written in Java language extended on the basis of AQS. It uses the state variable of AQS as the reentrant times of the lock. Whether the thread can obtain the lock depends on whether CAS can change the state from 0 to 1. After the thread holding the lock releases the lock, AQS will automatically wake up the thread in the waiting queue, and you don’t need to worry about ReentrantLock.