Today’s sharing started, please give us more advice ~

Let’s take a look at ReentrantLock first.

ReentrantLock

In ReentrantLock, the lock method is called to obtain the lock, and the unlock method is called to release the lock. Of course, the lock must be the same ReentrantLock.

ReentrantLock has fair and unfair locks, and we can see from the constructor that the lock constructed with no parameters is unfair.

Already implemented depends on Java synchronization framework AbstractQueuedSynchronizer (AQS) for short.

AQS maintains synchronization using an orthopedic volatile variable named state.

Therefore, we must first look at the underlying source code of AQS.

The bottom layer of AQS

Variables, constants, and inner classes of AQS

Head, tail, and an inner Node class are all covered here, but above we can also see an important state variable, the lock implementation mentioned earlier.

Because these three make up the bottom layer of AQS, a queue of bidirectional linked lists, the queue is used to store threads.

The Node inner class

There are not only prev, Next, thread, but also some variables representing the thread status, such as CANCELLED, CONDITION, PROPAGATE and SIGNAL. Note that this inner class is a final type and cannot be inherited. Prev, Next, and Thread are non-final and can be modified.

With that in mind, let’s go back to ReentrantLock.

Next, let’s look at the structure of ReentrantLock.

As you can see, there are three inner classes in ReentrantLock.

Respectively,

FairSync: Fair lock

NonfairSync: Unfair lock

Lock the Sync:

FairSync: Fair lock

As you can see, FairSync inherits Sync and has only a serialized Id property of its own (along with all of Sync’s properties and methods, of course).

The lock method

But most importantly, we still have to look at his lock method.

As you can see, he call AbstractQueuedSynchronizer acquire inside method, and gave it to a parameter is 1, because the Sync is inherited AbstractQueuedSynchronizer, AbstractQueuedSynchronizer is an abstract class, so is FairSync AbstractQueuedSynchronizer all properties and methods.

AbstractQueuedSynchronizer acquire method.

Next, let’s look at AbstratQueuedSynchronizer acquire method.

Let’s take a look at his notes

The exclusive mode is actually to acquire the lock. Call tryAcquire to acquire the lock. If the lock is successfully acquired, it will be returned; otherwise, the thread will queue.

The steps of the Acquire method are as follows

Call the tryAcquire method, which obtains the lock

Call the accquireQueued method. The previous determination is the non-return value of tryAcquire, so it means that no lock was acquired. Therefore, the next method is to enter the thread queue, that is, the underlying thread queue of AQS

If the result is True, the selfInterupt method is executed. If the result is True, it means that the lock cannot be obtained and the queue entry fails, so it needs to be interrupted

TryAcquire method

Can see this method is AbstractQueuedSynchronizer inside, and didn’t realize, in these classes are implemented.

Since we are looking at FairSync, the tryAcquire method called by FairSync must be from FairSync.

Here is his underlying implementation

Take a look at his notes

As you can see from the comments, tryAcquire here is a fair version, in line with the nature of FairSync. Next, analyze the source code (Acquire passed in with a fair lock is 1).

GetState method

It returns the underlying state variable, which is the state of AQS.

HasQueuedPredecessors method:

This method is used to determine whether a thread should be queued (return false not queued). This method is necessary because it is a fair lock. Otherwise, it would not be fair. The implementation of an unfair lock is simply the absence of this judgment.)

As you can see, this method is in the AQS, and the tail and head properties mentioned earlier are the head and tail of the underlying queue.

The key is the return statement

He has two main judgments:

Whether the head is equal to the tail

Whether the header ends empty, or whether the thread of the header is the current thread

Now, with this popup, it’s important to understand how nodes are added to the underlying queue.

Here is the addWaiter method for AQS

As you can see, if the tail is not empty, that means it’s not the first insert, then you just use the tail method. If it’s the first insert, you just call the ENq method, so the ENQ method is for the first insert, and both of these methods are AQS.

Can see there is an infinite loop, if it is inserted for the first time, to create a new node as the head node (the equivalent of a sentry), and then let the new tail node, this can be seen as the initialization end node, then the next cycle, as the end node is not null, then let the new nodes to the tail, let new nodes is also called the tail, Let the old tail point to the new tail.

But there is a question, if it is not the first time to join the team, why do you end up doing the enQ method again?

As we can see, what happens if the CAS fails if it is not the first time to join the team because addWaiter is used once? What if a thread changes the end of a node halfway through?

In this case, repeated operations are needed to continuously execute CAS until it succeeds. Therefore, enQ method must be executed no matter which insert is performed, because enQ method is an infinite loop of CAS execution, which will not end until CAS execution succeeds.

To sum up adding nodes:

So the head node is just a sentinel, it doesn’t store threads, and the insert method is a tail insert.

Now, the head node is not exactly the sentry, but when we look at the thread spin, we’ll see that the head node is actually the thread whose turn it is to execute, so here’s a hint.

Now that we know the insertion method, we go back to the return value of Hasqueuedestablished, which is how to determine whether we need to queue.

The steps are as follows:

Check whether the header is equal to the tail, and only when initialized will the header be equal to the tail, or if the queue is empty, both the head and tail are null, so if the header is equal to the tail (return false), then the queue is empty, so there is no need to queue.

Through the first judgment had already know there are other nodes exist between end node, but due to the front of the enq method is first tackle the head node, then in addition to the two CAS operation, other are not atomic, which is basically the whole enq methods are not atomic, so when is inserted into the first thread, There is an h.ext == null problem, so when h.ext == null it is queued.

A final judgment is whether the next thread head nodes is the current thread (namely next whether it’s your turn, do not need to queue, because insert interpolation method is to end, so the pop-up method from head to pop up), after twice in front of the judge, it is known that the queue of people waiting in line, and this time there is no queue insert node Null problem for the first time, Finally, you need to determine whether the current thread is next.

SetExclusiveOwnerThread method

The first thing to do is call the setExclusiveOwnerThread method to determine if there is no queue on the front or if the current thread is next.

The only thing this method does is change the ‘exclusiveOwnerThread’ to itself, indicating that the lock has been acquired by the thread.

To summarize the tryAcquire method:

1. Get the current thread first

2. Obtain the current lock status. 0 indicates that no lock is occupied

If no one is occupied, determine whether the current thread needs to queue

If the queue is empty, no queue is required

If the queue is not empty, but the next one is its turn, it does not need to be empty

Note here that null is inserted for the first time

3. If the current thread does not need to queue, the next step is to execute the CAS to obtain the lock (the lock state changes from 0 to 1). The CAS guarantees concurrent lock security.

If you get the lock, change the owner to yourself

4. Return true to the previous layer

5. If the lock is already occupied, i.e. state is not 0, there are two cases

Another thread owns the lock, so subsequent operations return false;

The current thread holds the lock, lock again, the equivalent of multilayer lock, and to modify the state of the lock state, in general will add 1 (plus acquire on the source, and acquire incoming is to 1), was released on behalf of the lock into the need for many times, just can be obtained by other threads, then return True to a layer.

In the second case, there is no need to use CAS for thread safety, because the thread is already secured by a layer of locks at the previous layer.

The entire tryAcquire method to determine whether to queue has ended

The next step is to go back to the upper acquire method

Returns the AbstractQueuedSynchronized acquire method

As you can see, if queuing is required, the acquireQueued method is executed, followed by addWaiter.

The addWaiter method has already been seen. It returns the inserted Node to the bottom queue, which is equivalent to the end Node, but it takes a Node.EXCLUSIVE argument.

But essentially this is just a state representation, which says join this node is in the wait state.

AcquireQueued method

The source code is as follows

Now let’s see what this method does.

AcquireQueued () ¶ If the queue is queued, the queue will be inserted and the acquireQueued will be executed. If the queue is queued, the park thread will be stopped. In ReentrantLock, this is done using spin.

In fact, this method is ReentranLock’s underlying spin operation.

Next, analyze the entire source code

In front of the spin cycle is very simple, but always spin down is not a good thing, because also consumes CPU spin, if there is a thread was too slow to release the lock, will be a lot of CPU, therefore, the spin is to limit the number of times, and the limited number of existed in when get lock failure may call two methods.

setHead

As you can see, the setHead method sets the thread inside the header to null, because the thread that acquires the lock does not exist in the queue, meaning that the thread that acquires the lock does not need to queue any more and does not need to be woken up.

ShouldParkAfterFailedAcquire method

As mentioned earlier, this thread is used to determine if the thread that failed to acquire the lock needs to park, that is, if it needs to block (because the CAS is too many times).

Let’s take a look at the source code

So what is the SIGNAL state

If the value is -1, the thread’s waitStatus is -1, which means that the thread is no longer spinning and will block or sleep.

This raises the question, why let the previous thread in the queue decide whether the current thread needs to sleep (why change the waitStatus of the previous thread to SIGNAL), i.e., suspend the current thread?

This is because waitStatus is an indicator of sleep and does not mean that the thread is actually suspended.

How many times does it take for the spin to go to sleep?

The answer is spin twice, the first spin, can’t grab the lock, treats the previous thread as sleep, changes its waitStatus to SIGNAL, and the second spin, the previous thread is already asleep, and suspends.

ParkAndCheckInterupt method

This method essentially suspends the thread and only executes it if it determines that the thread needs to be suspended

Until it calls the lockSupport. part method;

This method actually disables the thread, that is, suspends the thread.

cancelAcquire

As you can see from the acquireQueued method, after a series of try statements, there is a finally block that calls cancelAcquire and will not be executed until the failed variable is true.

The failed variable will only become false in a spin dead loop, not true, and the thread will never reach this line of code after suspension. When will the acquireQueued method be called?

We can see that the only way to execute the acquireQueued method here is if the try block throws an exception that interrupts the code inside the try, and the code inside the try does not change failed to true.

So where is this exception thrown?

If you look back all the way, there’s only one place where an exception was thrown

When tryAcquire is called to see if a queue is needed, an exception may be thrown if the thread that owns the lock attempts to acquire the lock again. However, the exception will be thrown if the lock state is less than 0.

I don’t know exactly what would make a lock less than zero;

Now look at what this cancelAcquire method does;

This method essentially tells the thread to cancel the lock acquisition;

The source code is as follows:

Today’s share has ended, please forgive and give advice!