JDK source code-AQS source code analysis, jdk source code aqs

Source: Internet
Author: User

JDK source code-AQS source code analysis, jdk source code aqs

Unless otherwise specified, all the articles on this site are original. For reprinted articles, enter the address.

 

AbstractQueuedSynchronizer (AQS) is the core of concurrent programming in JDK. ReentrantLock and CountDownLatch, which are often used in our work, are implemented based on it.

A two-way linked list (FIFO queue) is maintained in the AQS class, as shown in:

Each element in the queue is represented by a Node. We can see that there are several static constants in the Node class:

static final class Node {        /** Marker to indicate a node is waiting in shared mode */        static final Node SHARED = new Node();        /** Marker to indicate a node is waiting in exclusive mode */        static final Node EXCLUSIVE = null;        /** waitStatus value to indicate thread has cancelled */        static final int CANCELLED =  1;        /** waitStatus value to indicate successor's thread needs unparking */        static final int SIGNAL    = -1;        /** waitStatus value to indicate thread is waiting on condition */        static final int CONDITION = -2;        static final int PROPAGATE = -3;        volatile int waitStatus;        volatile Node prev;        volatile Node next;
volatile Thread thread; Node nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { } Node(Thread thread, Node mode) { this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; } }

In addition, AQS uses the volatile variable of a state to indicate the synchronization status.

So how does AQS implement lock operations through queues?

1. Lock acquisition

The following code is used to obtain the lock in AQS:

Public final void acquire (int arg ){
/** Get the lock through tryAcquire. If the lock is obtained successfully, selfInterrupt is terminated directly. Otherwise, the current thread is inserted into the queue.
* Node. EXCLUSIVE indicates creating an EXCLUSIVE Node.
*/If (! TryAcquire (arg) & acquireQueued (addWaiter (Node. EXCLUSIVE), arg) selfInterrupt ();}

However, AQS does not actually implement the above tryAcquire (arg) method. The specific lock acquisition operation must be implemented by its subclass, such as Sync in ReentrantLock:

 

Protected final boolean tryAcquire (int acquires ){
// Obtain the current Thread final Thread current = Thread. currentThread ();
// Get the state value (previously mentioned) int c = getState ();
// If the state is 0, no thread occupies the lock.
// If there is no element in the queue (because it is a fair lock, no judgment is made in the unfair lock, and the lock is directly obtained if the state is 0 ), CAS modify the current value if (c = 0) {if (! HasQueuedPredecessors () & compareAndSetState (0, acquires )){
// Identify the current thread to successfully obtain the lock setExclusiveOwnerThread (current); return true ;}}
// The state is not 0, and the thread occupying the lock is the current thread (here A reentrant lock concept is involved) else if (current = getExclusiveOwnerThread ()){
// Increase the number of retries int nextc = c + acquires;
// If the number overflows, an exception is thrown. if (nextc <0) throw new Error ("Maximum lock count exceeded"); setState (nextc); return true ;}
// If the lock has been occupied by other threads, return false if the lock fails to be obtained ;}

The above code comment mentions the concept of reentrant locks. reentrant locks are also called recursive locks. Simply put, the thread that has obtained the lock can also obtain the same lock again, we usually use the syschronized operation. ReentrantLock belongs to the reentrant lock. Spin locks are not reentrant locks.

Next, let's look at how AQS handles tryAcquire failures:

Private Node addWaiter (Node mode ){
// Create a queue Node = new node (Thread. currentThread (), mode); // obtain the Node pred = tail at the end of the current queue; if (pred! = Null ){
// The CAS operation tries to insert the Node to the waiting queue. Only one node is attempted here. prev = pred; if (compareAndSetTail (pred, node) {pred. next = node; return node ;}}
// If the add operation fails, enq performs a spin operation to confirm that the insert operation is successful. Enq (node); return node ;}
// Add the element to the end of the queue through a spin operation.
Private Node enq (final Node node) {(;;){
// Obtain the tail Node t = tail;
// If the end node is empty, the current queue is empty. You need to initialize the queue if (t = null ){
// Initialize the current queue if (compareAndSetHead (new Node () tail = head;} else {
// Insert the Node through the CAS operation, set the Node as the end Node of the queue, and return the node Node. prev = t; if (compareAndSetTail (t, node) {t. next = node; return t ;}}}}
/**
* If the inserted node is in front of the head, try to get the lock,
*/
Final boolean acquireQueued (final Node, int arg) {boolean failed = true; try {boolean interrupted = false;
// Spin operation (;;){
// Obtain final Node p = node. predecessor ();
// If the front node is head, try to obtain the lock if (p = head & tryAcquire (arg )){
// Set the head to the current node, indicating that the lock is successfully obtained. setHead (node); p. next = null; // help GC failed = false; return interrupted ;}
// Whether to suspend the current thread. if yes, the thread if (shouldParkAfterFailedAcquire (p, node) & parkAndCheckInterrupt () interrupted = true ;}} finally {if (failed) cancelAcquire (node );}}

The above code is a bit complicated. Here we will explain that the previous addWaiter code has added node to the waiting queue, so we need to suspend the node queue and wait for wake-up. The head node of the queue represents the node that currently occupies the lock. First, judge whether the pre-node of the inserted node is the head. If yes, try to obtain the lock (tryAcquire ), if the get succeeds, set the head to the current node. If the get fails, you need to determine whether to suspend the current thread.

 
/**
* Determine whether the current thread can be suspended
*/
Private static boolean shouldParkAfterFailedAcquire (Node pred, Node node ){
// Ws is the status of the node's front node int ws = pred. waitStatus; if (ws = Node. SIGNAL) // if the status of the front node is SIGNAL, the current node can be suspended and return true; if (ws> 0 ){

// Skip all CANCELLED nodes cyclically, locate a normal node, and rank the current node behind it

// GC recycles these CANCELLED nodes do {node. prev = pred. prev;} while (pred. waitStatus> 0); pred. next = node;} else {
// Change the status of the front node to SIGNAL
CompareAndSetWaitStatus (pred, ws, Node. SIGNAL);} return false ;}

 

// Use LockSupport to suspend the Thread and wait for the private final boolean parkAndCheckInterrupt () {LockSupport. park (this); return Thread. interrupted ();}

 

 

Ii. Lock release

With the foundation for obtaining the lock, it is easier to see the source code for releasing the lock. The following code executes the release Lock operation in AQS:

// Unlock Release Operation
Public final boolean release (int arg)
// Try to release the lock. tryRelease is also implemented by the subclass here. if the lock fails, false if (tryRelease (arg) {Node h = head; if (h! = Null & h. waitStatus! = 0) unparkSuccessor (h); return true;} return false ;}

The following code attempts to release a lock:

Protected final boolean tryRelease (int releases ){
// Obtain the state value and release the int c = getState ()-releases; if (Thread. currentThread ()! = GetExclusiveOwnerThread () throw new IllegalMonitorStateException (); boolean free = false;
// If the difference is 0, the lock has been completely released. if (c = 0) {free = true;
// The following is set to null, indicating that no thread currently occupies the lock setExclusiveOwnerThread (null );}
// If c is not 0, the lock is not completely released. Modify the state value setState (c); return free ;}

After the lock is released, you also need to wake up a slave node in the queue:

Private void unparkSuccessor (Node node) {// modify the status of the current node to 0 int ws = Node. waitStatus; if (ws <0) compareAndSetWaitStatus (node, ws, 0); // find the next node to be awakened from the queue
// First, directly successor Node s = node. next;
// If the result is null or its waitStatus is greater than 0 (the lock has been discarded), we will traverse the entire queue,
// Obtain the first Node to be awakened if (s = null | s. waitStatus> 0) {s = null; for (Node t = tail; t! = Null & t! = Node; t = t. prev) if (t. waitStatus <= 0) s = t;} if (s! = Null)
// Wake up the node LockSupport. unpark (s. thread );}

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.