Java concurrent packet synchronization component ReentrantLock

foreword

ReentrantLock is a typical implementation of Java concurrent package display locks, also known as reentrant exclusive locks . It has the same basic behavior and semantics as implicit monitor locks accessed by synchronized methods and statements, and is equivalent to synchronized Use, but ReentrantLock provides a more powerful and flexible locking mechanism than synchronized, which can reduce the probability of deadlock.

Because ReentrantLock is a display lock that directly implements the Lock interface, it not only implements the blocking method of acquiring synchronization locks, but also realizes the function of non-blocking, response timeout or interrupt acquisition of synchronization locks. Further, ReentrantLock also implements In order to support fair lock (FairSync) and non-fair lock (NonfairSync), we can choose to use different fair strategies according to our needs when using it. The difference between fair locks and non-fair locks is that the lock acquisition of fair locks is sequential. . However, the efficiency of fair locks is often not as high as that of unfair locks. In the case of many threads accessing, fair locks show lower throughput.

 

ReentrantLock reentrant lock source code analysis

With the previous analysis of the AQS source code and the learning of how to customize the synchronizer, it is completely worthless to analyze the source code of ReentrantLock, because it is completely written in the way we customized the synchronizer before, so The difference is that it implements both fair lock and unfair lock mechanisms at the same time, and additionally provides many methods for querying the synchronization queue status for developers to use.


 

As can be seen from the above ReentrantLock class structure, first, ReentrantLock implements the Lock and Serializable interfaces, and the abstract static inner class Sync of ReentrantLock inherits the AQS base class, and then the static inner classes FairSync and NonfairSync respectively implement the logic of fair locks and unfair locks .

Construction method 

public ReentrantLock() {
       sync = new NonfairSync();//Nonfair locks are used by default
}

public ReentrantLock(boolean fair) { //What kind of lock is used according to the parameters
       sync = fair ? new FairSync() : new NonfairSync();
}

    It can be seen from the construction method that ReentrantLock uses unfair locks by default, for no other reason, just because the operating efficiency of unfair locks is higher and the throughput is higher. To use fair lock outside can only be specified by parameters.

Unfair lock acquisition

From the user's point of view, I analyze the source code from the top layer down, starting with the default unfair lock acquisition operation. It is worth noting that the acquisition operation of the ReentrantLock lock is defined as an abstract method in the inner class Sync, and its concrete implementation is provided by FairSync and NonfairSync. So let's first look at NonfairSync's implementation of the lock() method:

final void lock() {
	if (compareAndSetState(0, 1)) //try to acquire the lock directly,
		setExclusiveOwnerThread(Thread.currentThread()); //Set the current thread as the owner of the exclusive lock
	else
		acquire(1); //Take the normal process to acquire the lock
}

protected final boolean tryAcquire(int acquires) {
	return nonfairTryAcquire(acquires); //here nonfairTryAcquire is defined in the parent class Sync
}
   It can be seen from the implementation of the above method lock() that unfair locks acquire locks on the heap, before taking the normal AQS queue to wait to acquire the lock, it will directly try to acquire the synchronization lock through CAS quickly, in case it happens at this time The lock is released by other owning threads, and the waiting thread has not had time to acquire the lock, so the unfairness is also reflected here. The new thread trying to acquire the lock may preempt the thread in the waiting queue to acquire the lock. opportunity. Let's look down. Only after the direct attempt to acquire the lock fails, it will execute the acquire(int) method, the top-level entry method of AQS' exclusive acquisition of the lock. We know that the acquire(int) method will eventually be called and overwritten by us. The tryAcquire(int) method of tryAcquire(int) tries to acquire the lock and returns if it succeeds. If it fails, it joins the synchronous waiting queue and is blocked waiting to be awakened by the precursor. So let's look at the tryAcquire() method we overridden. The implementation of the tryAcquire() method here actually executes the nonfairTryAcquire(int) method in its parent class Sync, so we move to the nonfairTryAcquire(int) method in the abstract parent class Sync ()method.
final boolean nonfairTryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();//Get the synchronization state
	if (c == 0) { //state == 0, indicating that the lock is not in an idle state
                //Directly try to acquire the lock successfully, set it to be owned by the current thread
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
        //If the current thread is the thread that occupies the lock, it means reentrancy,
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires; //Add 1 for re-entry (acquires is fixed to 1)
		if (nextc < 0) // overflow
			throw new Error("Maximum lock count exceeded");
		setState(nextc); //Because there is no competition, you can directly modify the state
		return true;
	}
	return false;
}
    It is clear from the above code comments, so the logic of unfair lock acquisition is summarized as follows:
  1. Try to modify the synchronization state state to acquire the lock directly through CAS, successfully set the current thread as the owner of the lock, and fail to follow the acquire() and tryAcquire() logic of AQS.
  2. When taking the acquire() of AQS, the custom logic is executed by calling tryAcquire(), that is, different logic processing is performed by judging the synchronization state.
  3. If the synchronization state is idle, try to modify the synchronization state state to acquire the lock directly through CAS again. After success, set the current thread as the owner of the lock and return true.
  4. If the synchronization state is not idle, but is reentrant, add 1 directly to the synchronization state and return true.
  5. If the passing state is not idle or reentrant, it will be added to the synchronous waiting queue through the exclusive internal logic of AQS, waiting for wake-up.

fair lock acquisition

After analyzing the default unfair lock acquisition process, we then analyze the fair lock acquisition, which implements the lock() method in the FairSync inner class:

final void lock() {
	acquire(1);
}

protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
        //When the synchronization status bit is idle, it is also necessary to judge whether the current synchronization waiting queue has the next thread waiting to be executed that is not the current thread.
        //hasQueuedPredecessors method is very simple, if the queue is empty, or the current thread is the successor node of the head node, it returns false
	if (c == 0) {
		if (!hasQueuedPredecessors() &&
			compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
        //The processing of reentrancy is the same as that of unfair locks
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}
   Through the above source code of fair lock acquisition, we can conclude that its acquisition logic is as follows:
  1. Before entering the synchronization waiting queue, only the acquire() and tryAcquire() logic of AQS are used to try to acquire the synchronization lock. When trying to acquire the lock, different logic processing is performed by judging the synchronization state.
  2. If the synchronization state is idle, the synchronization waiting queue is also checked. If the synchronization queue is empty, or if the synchronization queue is empty, or the thread in the second place represents the next thread that is about to be eligible to acquire the lock, the synchronization lock will be acquired through CAS.
  3. If the synchronization state is not idle, but is reentrant, add 1 directly to the synchronization state and return true.
  4. In other cases, you need to enter the synchronous waiting queue and wait in line.

By analyzing the acquisition process of fair locks and unfair locks, we can see that there are at least two differences between them: the first difference is that in the process of unfair lock acquisition, there are two operations of forcibly using CAS to directly acquire locks. After the forced acquisition fails, it will willingly enter the synchronization waiting queue of the FIFO and wait in line honestly, and the fair lock can only wait in the synchronization state when the synchronization state is idle, and the synchronization queue is empty, or it is the next thread that is about to be eligible to try to acquire the lock. In other cases (excluding re-entrancy of course), it will directly enter the waiting queue and wait in line without hesitation. These two differences are also the differences between unfair locks and fair locks. To sum up, the so-called fairness is to try only when it is your turn, and never jump in the queue. The so-called unfairness means that before entering the sleep waiting queue, it will not miss any idle time that can be used, so that it can acquire the lock earlier. Unfair locks may cause a thread to be starved for a long time, but few threads are switched, which improves the success rate of lock acquisition and increases system throughput.

 

Release of ReentrantLock 

Unlike the lock acquisition process, the lock release process is the same for both fair locks and unfair locks, so let's take a look at ReentrantLock's lock release method unlock().

public void unlock() {
	sync.release(1);//The top-level entry method of the AQS exclusive lock release directly called
}

protected final boolean tryRelease(int releases) {
	int c = getState() - releases; // directly subtract releases
        //The owner of the lock must be judged before the real release, if the current thread is not in possession of the lock, an exception will be thrown
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) { //state ==0 means that the lock is completely released before other threads can acquire the lock
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}
    The lock release process is very simple. The top-level entry method of the lock release of the AQS exclusive lock is directly executed, and the real release operation is performed by calling back the custom tryRelease() method. Before releasing, it is necessary to judge whether the current thread occupies the lock, and after the reentry is completely released, the current lock owner will be emptied, and return true, and return false before it is not completely released.

 

For ReentrantLock's other non-blocking lock acquisition methods that respond to timeouts or interruptions, the principles are very simple, so I won't describe them one by one. In addition, the ReentrantLock class also provides a series of query methods for synchronous waiting queues and conditional waiting queues, which are not introduced here. You can refer to them when you need them.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326013214&siteId=291194637
Recommended