Introduction to the

AQS (AbstractQueuedSynchronized), that is, the queue synchronizer. It is the basic framework for building locks or other synchronization components (such as ReentrantLock, ReentrantReadWriteLock, Semaphore, etc.), and the basis for most synchronization requirements that JUC and packet authors expect. It is the core foundational component in JUC and packet distribution.

CLH queue lock

CLH queue locks are Craig, Landin, and Hagersten(CLH)locks. These are the names of three people. At the same time, it is also the realization mechanism of the lock in the PC. AQS in Java is a variant implementation based on CLH queue locks.

CLH queue locking is also a scalable, fair spin lock based on a one-way linked list. The applying thread spins only on a local variable, constantly polling the state of the precursor, and terminating the spin if it finds that the precursor releases the lock.

1) There is now a queue, and each QNode in the queue corresponds to a thread requesting the lock. QNode contains two properties, myPre (references to the precursor nodes) and Locked (whether locks are required).

2) When multiple threads want to acquire locks, they will be placed in the queue in the order requested. Also point myPre to a reference to the precursor node

3) The thread does a constant spin query on its myPre to see if the precursor releases the lock. The lock is acquired as soon as the precursor point is found to release the lock (Locked = false).

4) After the subsequent node obtains the lock, the original precursor point is removed from the queue.

AQS design pattern

AQS itself is an abstract class. Its main use mode is inheritance. A subclass inherits AQS and implements its internally defined abstract methods.

ReentrantLock and ReentrantReadWriteLock are actually implemented internally based on AQS.

At this point, combining the source code and previous learning, the two do not inherit AQS directly, but extend the static inner class inside it to inherit AQS. The idea behind this is to make it easier for users to perform lock operations by distinguishing between users and implementers.

Locks are user-oriented and define how the lock interacts with the user while hiding the implementation details. And AQS is for the implementer of the lock, its internal completion of the implementation of the lock. By distinguishing between locks and synchronizers, users and implementers can better focus on their respective domains.

The realization idea of AQS

The AQS design pattern uses the template design pattern. Through the source code can be seen, in AQS, there is no specific implementation of the method, these methods are developers to achieve their own.

The template design pattern is very much involved in development. It simply means that you define the skeleton of a process in a method, and let the implementation of the process be done in subclasses. Spring, for example, uses a lot of template design patterns internally, such as JDBCTemplate, RedisTemplate, RabbitTemplate, and so on.

Template pattern implementation

Public abstract class AbstractCake {protected abstract void shape(); protected abstract void apply(); protected abstract void brake(); /* Template method */ public final void run(){this.shape(); this.apply(); this.brake(); } protected boolean shouldApply(){ return true; }}Copy the code
Public class CheeseCake extends AbstractCake {@override protected void shape() { System.out.println(" cheesecake shape "); } @override protected void apply() {system.out.println (" cheesecake "); } @override protected void brake() {system.out.println (" cheesecake bake "); }}Copy the code
Public class CreamCake extends AbstractCake {@override protected void shape() {system.out.println (" CreamCake "); } @override protected void apply() {system.out.println (" butter "); } @override protected void brake() {system.out.println (" custard cake "); }}Copy the code
Public static void main(String[] args) {AbstractCake cake1 = new CheeseCake(); AbstractCake cake2 = new CreamCake(); cake1.run(); cake2.run(); }}Copy the code

As you can see from the above implementation, you only need to define a custom abstract class that defines the skeleton of the execution process. It can then be implemented differently through implementation classes. This implementation idea is the template design pattern.

Template patterns in AQS

According to the above, there is a lot of use of template design patterns in AQS. Acquire (int ARg), Release (int ARg), acquireShared(int ARg), etc are all template methods.

Its internal template methods can be roughly divided into three categories:

  • XxSharedxx: Shared access and release, such as read lock.
  • Acquire: Exclusive acquire and release, such as write lock.
  • Query the status of waiting threads in the synchronization queue.

AQS synchronization status

AQS operates on the lock through a synchronous state switch, in which a variable, state, is used to represent the state of the lock acquired by the thread. When state is greater than 0, the current thread obtains resources. When state is 0, the current thread releases resources.

In multithreading, there must be multiple threads to modify the state variable at the same time. Therefore, AQS also provides some methods to safely modify the state value, which are as follows:

AQS implementation principle

The Node Node

As mentioned earlier, AQS is based on the idea of CLH queue locking, which is internally different from CLH unidirectional linked lists, but uses bidirectional linked lists. So for a queue, there must be a node to keep the thread information, such as: front-drive node, back-drive node, current thread node, status and other information.

AQS internally defines a Node object to store this information.

There are two thread wait modes:

  • SHARED: a thread waits for a lock in SHARED mode, such as a read lock
  • EXCLUSIVE: A thread waits in EXCLUSIVE mode for a lock, such as a write lock

Five thread states:

  • Initialize the Node object. The default value is 0
  • CANCELLD: indicates that the thread’s lock request has been cancelled. The value is 1
  • SINNAL: indicates that the thread is ready and waiting for the lock to be free to me. The value is -1
  • CONDITION: indicates that the thread waits for a CONDITION to be satisfied. The value is -2
  • PROPAGETE: This state takes effect only when a thread is in SHARED mode, propagated, and has a value of -3

Five member variables

  • WaitStatus: indicates the status of the thread in the queue. The value corresponds to the above five thread states
  • Prev: indicates the precursor node of the current thread
  • Next: represents the back-end node of the current thread
  • Thread: indicates the current thread
  • NextWaiter: indicates the node waiting for the condition

Meanwhile, AQS also has two member variables, head and tail, which represent the first node and the last node of the team respectively.

The operation of the node in the synchronization queue

When multiple threads concurrently compete for the synchronous state lock, AQS will pack the thread that fails to acquire the lock into a Node and put it at the end of the queue according to the FIFO principle of the queue.

AQS provides a method to set tail nodes based on CAS, compareAndSetTail (Node expect, Node Update), which needs to pass the desired tail nodes and the current Node. When true is returned, The current node connects to the previous tail node in the queue.

At this point, it can be found that the head node must be the node that can successfully obtain the lock. When the head node releases the lock, it will wake up its successor node. When the successor node successfully obtains the lock, the pointer of the head node will point to the successor node as the head node of the current queue, and then remove the original head node from the queue.

For this process, only one thread can get the synchronization state, so there is no need for CAS to guarantee atomicity, just remove the header pointer and disconnect the original reference connection.