Synchronized is hosted for JVM execution, and locking is done in code. ReetrantLock is the main implementation class of Lock. ReetrantLock is a reentrant Lock. It can specify both fair and unfair locks. So let’s see how he does it.

How to use ReentrantLock

ReentrantLock lock = new ReentrantLock(); // If the lock is held by another thread, the block will wait for the lock to be released lock.lock(); Try {// operation} catch (Exception e) {e.printStackTrace(); } finally {// unlock lock.unlock(); }Copy the code

Create a ReentrantLock object. Lock () is used to lock and unlock() is used to unlock.

It can be locked in three ways, using lock, trylock, and trylock(long, TimeUnit) to specify time parameters. When you use a lock to acquire a lock, if it is held by another thread, it is in a wait state. In addition, we need to call the unlock method to release the lock actively. Even if an exception occurs, it will not release the lock actively. We need to explicitly release the lock. The trylock method returns true on success and false on failure. Use trylock(long, TimeUnit) to obtain the lock. Return true if the lock is obtained during the wait time and false if the lock is obtained after timeout. You can also call the lockInterruptibly method to interrupt the lock and interrupt the wait state of a thread if it is waiting to acquire the lock.

What is a reentrant lock

ReentrantLock is a ReentrantLock. What is a ReentrantLock? Reentrant locking means that a thread can repeatedly lock the same lock without blocking, thus avoiding deadlocks. Let’s take a look at the code that validates a reentrant lock:

public static class TestReentrantLock {
	private Lock lock = new ReentrantLock();
	public void method() {
		lock.lock();
		try {
			System.out.println("Method 1 obtain the ReentrantLock lock");
			method2();
		} finally {
			lock.unlock();
		}
	}
	public void method2() {
		lock.lock();
		try {
			System.out.println("Method 2 ReentrantLock"); } finally { lock.unlock(); } } public static void main(String[] args) { new TestReentrantLock().method(); }}Copy the code

As you can see from the code above, ReentrantLock is reentrant. So what is the underlying implementation of this reentrant?

An underlying implementation of a reentrant lock

ReentrantLock uses the value of state in the AQS. A thread can keep locking to increase the value of state, and correspondingly unlock to unlock until state is zero. The value of state is volatile, and the source code is as follows.

/ / Java. Util. Concurrent. The locks. Already. FairSync protected final Boolean tryAcquire (int acquires) {/ / get the current thread final Thread current = Thread.currentThread(); int c = getState(); // The current lock is not occupiedif(c == 0) {//1. Check whether any node is waiting in the synchronization queueif(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {//2. If up! If 1 is true, change the state value (indicating that the current lock is occupied) //3. If 2 is true, change the current lock occupying thread to the current threadsetExclusiveOwnerThread(current);
 		   return true; }} // Hold lock thread == current thread (reentrant)else if(current == getExclusiveOwnerThread()) { int nextc = c + acquires; //if (nextc < 0)
 		   throw new Error("Maximum lock count exceeded"); / / change the statussetState(nextc);
 	   return true; } // Failed to get the lock directlyreturn false;
}

Copy the code

Three, what is a fair lock

We said that ReentrantLock can implement fair and unfair locking, so what is fair locking? What is an unfair lock? The so-called fair lock is that when multiple threads request to obtain the same resource, it can ensure that the thread requests in the order of execution, to ensure the effect of fair competition. An unfair lock, on the other hand, is one in which each thread fights for the lock in a different order.

In ReentrantLock, fairness is implemented by inheriting AQS.

AQS

AQS is an abstract class that is used primarily through inheritance. The functions of AQS fall into two categories: exclusive and shared. The realization of AQS depends on the internal synchronization queue, that is, the two-way queue of FIFO. If the current thread fails to compete for the lock, then AQS will construct the current thread and wait state information into a Node and join the synchronization queue, and then block the thread. When the thread that acquired the lock releases the lock, a blocked node (thread) is awakened from the queue. AQS uses a member variable state of type int to indicate the synchronization state. When state>0, the lock has been acquired, and when state = 0, the lock has been released.

When there is a lock contention, when a new thread is added, the thread is encapsulated as a Node Node and appended to the synchronization queue. The leading pointer of the new thread points to the previous Node, and the trailing pointer of the previous Node points to the Node of the new thread. It is CAS that makes the pointer modification.

When the head node releases the lock, it will wake up the successor node. If the successor node successfully obtains the lock, it will set itself as the head node. Setting the head node does not need CAS.

Fair lock source code

Joins the synchronization queue (the lock is acquired directly when the synchronization queue is empty) and waits for the lock

//java.util.concurrent.locks.ReentrantLock.FairSync
final void lock() {
	acquire(1);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
	if(! tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

TryAcquire (): template method to obtain the lock

/ / Java. Util. Concurrent. The locks. AbstractQueuedSynchronizer / / 1 private Node addWaiter (Node mode) {/ / generated Node Node Node = new  Node(Thread.currentThread(), mode); Node pred = tail;if(pred ! = null) {// Add node to queue.if (compareAndSetTail(pred, node)) {
 		   pred.next = node;
 		   returnnode; }} // If join fails (multi-thread race or tail pointer is null) enq(node);returnnode; } //1.1 Private Node enq(final Node Node) {// Add Node in an infinite loop (CAS fails)for (;;) {
 	   Node t = tail;
 	   if(t == null) {//tail is null, synchronizes queue initialization // sets head pointerif(compareAndSetHead(new Node())) tail = head; // add tail to head}else{ node.prev = t; // Add the current node to the end of the queueif (compareAndSetTail(t, node)) {
 			   t.next = node;
 			   returnt; }}}} //2 Final Boolean acquireQueued(final Node Node, int arg) {Boolean failed =true; Try {// indicates whether interrupted Boolean interrupted =false;
 	   for(;;) {// get node.pre node final node p = node.predecessor();if(p == head // whether the current node is the second node in the synchronization queue && tryAcquire(ARG)) {// Get the lock,head points to the current nodesetHead(node); //head=head.next p.next = null; // Empty failed =false;
 			   return interrupted;
 		   }

 		   if(shouldParkAfterFailedAcquire (p, node) && / whether/idling (as idle wake is a time-consuming operation, enter the idle state before judging the pre node. If the pre node is about to release the lock, it does not enter idle.) parkAndCheckInterrupt())// Use unsafe.park() to idle (block) interrupted =true; }} finally {if thread.interrupt () is called,(it is not really interrupted, and the loop continues until the lock is acquired)}} finally {ifCancelAcquire (node) cancelAcquire(node) cancelAcquire(node) }}Copy the code

AcquireQueued (addWaiter(Node.exclusive), ARg): Joins the synchronization queue

/ / Java. Util. Concurrent. The locks. AbstractQueuedSynchronizer / / 1 private Node addWaiter (Node mode) {/ / generated Node Node Node = new  Node(Thread.currentThread(), mode); Node pred = tail;if(pred ! = null) {// Add node to queue.if (compareAndSetTail(pred, node)) {
 		   pred.next = node;
 		   returnnode; }} // If join fails (multi-thread race or tail pointer is null) enq(node);returnnode; } //1.1 Private Node enq(final Node Node) {// Add Node in an infinite loop (CAS fails)for (;;) {
 	   Node t = tail;
 	   if(t == null) {//tail is null, synchronizes queue initialization // sets head pointerif(compareAndSetHead(new Node())) tail = head; // add tail to head}else{ node.prev = t; // Add the current node to the end of the queueif (compareAndSetTail(t, node)) {
 			   t.next = node;
 			   returnt; }}}} //2 Final Boolean acquireQueued(final Node Node, int arg) {Boolean failed =true; Try {// indicates whether interrupted Boolean interrupted =false;
 	   for(;;) {// get node.pre node final node p = node.predecessor();if(p == head // whether the current node is the second node in the synchronization queue && tryAcquire(ARG)) {// Get the lock,head points to the current nodesetHead(node); //head=head.next p.next = null; // Empty failed =false;
 			   return interrupted;
 		   }

 		   if(shouldParkAfterFailedAcquire (p, node) && / whether/idling (as idle wake is a time-consuming operation, enter the idle state before judging the pre node. If the pre node is about to release the lock, it does not enter idle.) parkAndCheckInterrupt())// Use unsafe.park() to idle (block) interrupted =true; }} finally {if thread.interrupt () is called,(it is not really interrupted, and the loop continues until the lock is acquired)}} finally {ifCancelAcquire (node) cancelAcquire(node) cancelAcquire(node) }}Copy the code

SelfInterrupt (): Wakes up the current thread

static void selfInterrupt() {// Respond to intterpt() request thread.currentThread ().interrupt(); }Copy the code

This is the ReadWriteLock principle and source code interpretation, I hope you have a harvest.

Part of the content: juejin.cn/post/684490…