In Java concurrency, we are exposed to utility classes such as ReentranLock, CountDownLatch, Semaphore, and Condition. And these tools are all stuff to the other disciples, their common master is our this article AQS, full name: AbstractQueuedSynchronizer. This AQS is the framework on which almost all synchronizer and lock implementations in Java depend. During interviews, many interviewers will ask about this AQS. If say you can explain very clearly come out AQS, that but additional points.

When learning a new framework or technology, there are actually three points to understand.

1. What is this technical framework definition?

2. What does it do?

3. What is the implementation principle?

Referred to as the learning axe! We just have to figure out these three points. That’s enough to add to your knowledge base. After the practice to be familiar with and precipitation down, be well acquainted with, can learn to apply.

So let’s first understand what AQS are. Start with the source code. I’ll take a screenshot from the class’s comments.

Provides a framework for implementing blocking locks and synchronizers by relying on fifO wait queues. The purpose of this class is to design a useful basic framework for most synchronizers of all kinds, implemented with simple atomic state markers. Subclasses must define methods that change the status flag. The method includes rules for getting and releasing states. With these two methods, and the rest of the class’s methods, you can do all the queuing and blocking. Subclasses can contain other state values, but only the getState,setState, and compareAndSetState methods can be used to achieve synchronization.

By the way, when we look at the source code, we can know the design ideas of this class by reading the annotations of the class, and many of the popular science of some technologies on the Internet, many are directly translated from the English annotations of the source code. So, as long as we chew down the English of these introductions, understand. That’s the best way to understand the point.

Through this definition, we can know the design purpose of AQS. And the role is clear. Is the basic framework for implementing concurrent synchronizers and locks. Now, let’s look at how it works.

There are two key points in AQS implementation, one is the state field of the lock, and a fifO queue implemented by linked list. State is used to indicate the status of the lock, and queues are used to queue waiting threads. AQS implements setState,getState and other state-related methods, as well as queue methods such as adding and removing nodes, and so on.

However, AQS is an abstract class that provides methods that need to be subclassed. Other locks and related synchronizers can implement their own lock and synchronizer functions by overriding the methods of the parent class. AQS provides exclusive mode and share mode. This is the source of what we often call exclusive and shared locks. Here’s how the AQS framework needs to subclass:

TryAcquire // Obtain the lock tryRelease // Release the lock tryAcquireShared // Obtain the shared lock tryReleaseShared // Release the shared lock isHeldExclusively // Whether you have an exclusive lockCopy the code

To better understand these methods, let’s take a look at the ReentrantLock source code to see how it overwrites the AQS method to implement the lock.

ReentrantLock also provides two modes of fair and unfair locking. Default no arguments are unfair locks.

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

Sync is an inner class of ReentrantLock, which inherits AQS. The NonfairSync and FairSync classes are subclasses that inherit from Sync, and tryAcquire() is the AQS lock method. FairSync overrides the tryAcquire() method, while NonfairSync is a tryAcquire() method that uses its parent Sync class. Let’s start with the tryAcquire() method of NonFairSync:

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 (c == 0) {if (c == 0) {if (c == 0) {if (c == 0) { Even though unsafe, you still need to call compareAndSetState to change the state. The compareAndSetState implementation is the unsafe class, and you can default to thread-safe. If (compareAndSetState(0, acquires)) {// Set the current thread to the thread that acquires the lock. setExclusiveOwnerThread(current); return true; }} // The thread that has acquired the lock will find that it is the thread that acquired the lock. Else if (current == getExclusiveOwnerThread()) {// Add state to acquires, int nexTC = c + acquires; If (nextc < 0) // Overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }Copy the code

Unfair lock Implementation here is based on the state of AQS. In the definition of AQS, state is an important attribute. This state is the thread ownership flag, which is used to control the thread from being affected by other threads. In exclusive mode, when another thread acquires that state has been changed, it means that the first thread is already executing the code and can no longer execute it.

Let’s look at the tryAcquire() implementation of fair locks.

protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); If (c == 0) {// The only difference here is hasqueuedToraise, hasqueuedtoraise, // Determine whether there is a thread queuing inside the queue if (! hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } 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

As we can see, a fair lock requires the next thread from the queue to execute, and the queue is first-in, first-out, so fairness is guaranteed.

Consider the tryRelease() method that releases the lock. In both the NonfairSync and FairSync classes, the lock release method uses the Sync parent.

Protected final Boolean tryRelease(int releases) {// Subtract the number of times the lock needs to be released from state, And then I get the new value for state int C = getState() -releases; If (thread.currentThread ()! = getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; If (c == 0) {free = true; if (c = 0) {free = true; setExclusiveOwnerThread(null); } setState(c); return free; }Copy the code

At this point, we see how ReentrantLock overrides the methods used to acquire and release locks. Methods like setState and setExclusiveOwnerThread are all methods in AQS. There is no need for ReentranLock side to implement. ReentranLock overwrites tryAcquire and tryRelease in AQS to achieve its own functions.

Let’s take a look at another concurrent tool class that implements its own functionality using the tryAcquireShared and tryReleaseShared shared locks: CountDownLatch.

CountDownLatch allows a specified number of threads to complete the task before the main thread moves on to the next task. The common way to do this is

CountDownLatch.countDown()

CountDownLatch.await()

CountDown () We’ll start with two approaches, starting with countDown()

Public void countDown() {//releaseShared is a method implemented by the parent AQS. sync.releaseShared(1); }Copy the code

CountDownLatch here also uses an internal class Sync to inherit AQS

Public final Boolean releaseShared(int arg) { TryReleaseShared if (tryReleaseShared(arg)) {// If (tryReleaseShared(arg)) {// If (tryReleaseShared(arg)) {// If (tryReleaseShared(arg)) { But this queue is not actually used in CountDownLatch //. doReleaseShared(); return true; } return false; } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1:1; } protected boolean tryReleaseShared(int releases) { // Decrement count; Signal when transition to zero If the state value is 0, return true, indicating that the threads in CountDownLatch have finished executing for (;). { int c = getState(); if (c == 0) return false; int nextc = c-1; If (compareAndSetState(c, nexTC)) // If (compareAndSetState(c, nexTC)) return nextc == 0; }}Copy the code

The countDown method simply subtracts state by one. Let’s look at the await() method.

public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } / / acquireSharedInterruptibly is AQS method, Here is also called the public / / tryAcquireShared method final void acquireSharedInterruptibly (int arg) throws InterruptedException {the if (Thread.interrupted()) throw new InterruptedException(); If (tryAcquireShared (arg) < 0) / / superclass method doAcquireSharedInterruptibly (arg); } /** * Acquires in shared interruptible mode. * @param arg the acquire argument */ private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); // Loop to get state to see if it has been released. Propagate(node, r); propagate (node, r); p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); }}Copy the code

Await () method is invoked to AQS doAcquireSharedInterruptibly method of this method in the key place I have comments. Essentially, it breaks out of the loop when state goes to 0. The effect of CountDownLatch becomes that the thread is finished executing. So, simply put, CountDownLatch starts with the input parameter. If the number is 3, assign 3 to the state, and at the end of each execution the state is reduced by one. So when state is equal to 0, we’re done. You’re good to go.

With these two concurrent classes, we are now impressed with the implementation of AQS, which is state and queue. However, in this article, there is no analysis of all methods in AQS, which requires readers to read the source code. It is not that the author is lazy, but I think that in order to really precipitate these knowledge into their own knowledge, they need to practice, to read, will be deeply impressed in their mind.

Why do you sometimes read an article and think you know something, but you can’t explain it clearly in an interview? Just because your knowledge is not solid. And let knowledge solid have quite much way, for instance you carry knowledge point down, but if be oneself go understanding read, try to sum up again, that meeting is better than carry knowledge point down. Can also better understand the author’s train of thought.

It is better to teach a man how to fish than to give him a fish. See xiaoxia and you next article