Devil in detail, understand Java concurrent bottom AQS implementation

jdk's JUC package (java.util.concurrent) provides a large number of Java concurrent tools for use, which are basically written by Doug Lea. Many places are worth learning and learning. It is the only way to upgrade.

Starting with the common object locks, the use of concurrent tools and their functional characteristics in JUC package, this paper analyses concurrent AQS abstract class implementation step by step from shallow to deep with problems.

Noun Interpretation

1 AQS

AQS is an abstract class, full path class java.util.concurrent.locks.AbstractQueuedSynchronizer, Abstract queue synchronizer, is a concurrent tool abstract class based on template pattern. The following concurrent classes are implemented based on AQS:

2 CAS

CAS is the abbreviation of Conmpare and Swap, which is an atomic operation instruction.

Three basic operands are used in the CAS mechanism: memory address addr, expected old value oldVal, and new value to be modified. When updating a variable, only when the expected value of the variable oldVal is the same as the actual value in the memory address addr will the value corresponding to the memory address addr be changed to new Val.

Based on the idea of optimistic locks, Variable-valued threads can be safely updated by CAS through repeated attempts and comparisons.

3 Thread Interrupt

Thread interruption is a thread collaboration mechanism used to collaborate with other threads to interrupt the execution of tasks.

When a thread is in a blocked waiting state, such as calling wait(), join(), sleep(), after calling the thread's interrupt() method, the thread immediately exits the blocking and receives the InterruptedException.

When the thread is running and interrupt() method of the thread is called, the thread will not interrupt immediately. It needs to detect the interrupt flag by calling isInterrupted() method in the thread's specific task execution logic, and then actively respond to the interrupt, usually throwing the InterruptedException.

Object Lock Characteristics

The basic features of object locks and concurrency tools are described first, and then how to implement these features is expanded step by step.

1 Explicit Acquisition

Taking the ReentrantLock lock as an example, we mainly support the following four ways to explicitly acquire locks

  • (1) Blocking waiting for acquisition
ReentrantLock lock = new ReentrantLock();
// Wait until you succeed
lock.lock();
  • (2) Non-blocking attempt acquisition
ReentrantLock lock = new ReentrantLock();
// Attempt to acquire the lock. If the lock has been occupied by another thread, it will not block and wait to return directly to false.
// Return true - The lock is idle and fetched by the thread, or has been held by the thread.
// Return false - Acquisition lock failed
boolean isGetLock = lock.tryLock();
  • (3) Blocking waiting for acquisition within a specified time
ReentrantLock lock = new ReentrantLock();
try {
    // Attempt to acquire locks within a specified time
    // Return true - The lock is idle and fetched by the thread, or has been held by the thread.
    // Returns false - No lock was acquired within a specified time
    lock.tryLock(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    // Internal call isInterrupted() method detects thread interrupt flags and actively responds to interrupts
    e.printStackTrace();
}
  • (4) Response interrupt acquisition
ReentrantLock lock = new ReentrantLock();
try {
    // Response to interrupt acquisition locks
    // If the thread.interrupt() method of the thread is called to set the thread interrupt, the thread exits the blocking wait and throws an interrupt exception.
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    e.printStackTrace();
}

2 Explicit release

ReentrantLock lock = new ReentrantLock();
lock.lock();
// ... Various business operations
// Explicit release lock
lock.unlock();

3 reentrant

Threads that have acquired the lock are requested to obtain the lock directly again

4 Sharable

It means that the same resource allows multiple threads to share. For example, read-write locks allow multiple threads to share. Shared locks allow multiple threads to access data concurrently and safely, and improve program execution efficiency.

5. Fairness and Unfairness

Fair Lock: Multiple threads compete for locks in a first come, first served and fair manner. Before each lock, the waiting queue is checked to see if there are threads in the queue, and no attempt is made to acquire the lock. Unfair Locks: When a thread acquires locks in an unfair way, it first tries to acquire locks instead of waiting. If you don't succeed, you're in the waiting queue.

Because unfair locking can make subsequent threads have a certain probability of acquiring locks directly, reduce the probability of thread hanging and waiting, and its performance is better than fair locking.

AQS Implementation Principle

1 Basic concepts

(1) Condition interface

Object-like wait(), wait(long timeout), notify() and notifyAll() methods combined with synchronized built-in locks can realize the wait/notification mode. Object locks such as ReentrantLock and ReentrantReadWriteLock which implement Lock interface also have similar functions:

Condition interface defines await(), awaitNanos(long), signal(), signalAll() and other methods, and implements wait/notification function with object lock instance. The principle is to implement Condition interface based on AQS internal class ConditionObject. Thread await blocks and enters CLH queue (mentioned below), waiting for other threads to call signal method and be waked up.

(2) CLH queue

CLH queue. CLH is short for Craig, Landin, Hagersten, the author of the algorithm.

AQS maintains a two-way FIFO CLH queue. AQS relies on it to manage waiting threads. If a thread fails to obtain synchronized competing resources, it will block threads and join the CLH synchronization queue. When competing resources are idle, it will block threads and allocate resources based on the CLH queue.

The head node of CLH saves the thread currently occupying resources, or there is no thread information, while other nodes save the queued thread information.

The waitStatus of each node in CLH is as follows:

  • CANCELLED(1): Indicates that the current node has been cancelled. When timeout or interruption occurs (in the case of response interruption), a change to this state is triggered, and the node entering that state will not change again.
  • SIGNAL(-1): Indicates that the successor node is waiting for the current node to wake up. The status of the precursor node is updated to SIGNAL before the successor node enters the dormant state after joining the queue.
  • CONDITION(-2): Indicates that the node is waiting on Condition. When other threads call the signal() method of Condition, the node in the CONDITION state will move from the waiting queue to the synchronization queue, waiting for the acquisition of the synchronization lock.
  • PROPAGATE(-3): In shared mode, the precursor node wakes up not only its successor node, but also its successor node.
  • 0: The default state of the new node when it joins the queue

(3) Resource sharing

AQS defines two ways of resource sharing: Exclusive exclusive, only one thread can execute, such as ReentrantLock Share sharing, multiple threads can execute at the same time, such as Semaphore/CountDownLatch

(4) The way to block/wake up threads

AQS blocks threads based on Park method provided by sun.misc.Unsafe class, and unpark method wakes up threads. Threads blocked by park method can respond to interrupt() interrupt request to exit blocking.

2 Basic Design

Core Design Idea: AQS provides a framework for implementing blocking locks and associated concurrent synchronizers that depend on CLH queues. Subclasses implement protect methods to determine whether resources can be acquired or released. AQS implements thread scheduling strategies based on these protect methods to queue and wake up threads.

AQS also provides an int-type variable to support thread-safe atomic update as a synchronous state value. Subclasses can flexibly define the meaning of the variable to be updated according to actual needs.

The series of protect methods redefined by subclasses are as follows:

  • boolean tryAcquire(int) exclusive way to try to get resources, success returns true, failure returns false
  • boolean tryRelease(int) exclusive attempt to release resources, success returns true, failure returns false
  • The int try AcquireShared (int) sharing method attempts to obtain resources. Negative numbers denote failure; 0 denotes success, but no remaining resources are available; positive numbers denote success, with remaining resources
  • Boolean tryRelease Shared (int) Shared attempts to release resources. If it is released, it allows the wake-up of subsequent waiting nodes to return true, otherwise it returns false.

These methods are always invoked by threads that need to schedule collaboration, and subclasses need to redefine these methods in a non-blocking manner

Based on the above tryXXX method, AQS provides the following methods to acquire/release resources:

  • void acquire(int) monopolizes access to resources, and threads return directly. Otherwise, they enter the waiting queue until resources are acquired, and the whole process ignores the impact of interruptions.
  • Threads release resources in boolean release(int) exclusive mode, releasing a specified amount of resources first, if completely released (that is, state=0), it will wake up other threads waiting in the queue to obtain resources.
  • void acquireShared(int) exclusive access to resources
  • boolean releaseShared(int) Sharing to release resources

Take the monopoly mode as an example: the core realization of acquiring/releasing resources is as follows:

 Acquire:
     while (!tryAcquire(arg)) {
        If the thread is not queued, it is added to the queue.
     }

 Release:
     if (tryRelease(arg))
        Wake up the first queuing thread in CLH

Here, a little bit around, the following picture to Re-stroke the design ideas described above:

Feature Realization

Following is the implementation principle of a series of functional features of object lock and concurrency tool based on AQS.

1 Explicit Acquisition

This feature also takes the ReentrantLock lock as an example. ReentrantLock is a re-entrant object lock. Threads acquire a successful lock every time they request it. The synchronization state value is added to 1, the release lock state is reduced by 1, and the state 0 represents that no thread holds a lock.

ReentrantLock locks support fairness/unfairness. The following explicit acquisition features take fair locks as an example

(1) Blocking waiting for acquisition

The basic realization is as follows:

  • 1. ReentrantLock implements the tryAcquire(int) method of AQS. Judge first that if no thread holds a lock, or if the current thread already holds a lock, it returns true or false.
  • 2. AQS acquire(int) method determines whether the current node is head ed and whether resources can be obtained based on tryAcquire(int). If not, it joins CLH queue to block and wait.
  • 3. The lock() method of ReentrantLock blocks the acquire(int) method based on AQS to wait for the acquisition lock.

The tryAcquire(int) method in ReentrantLock implements:

protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
    // No thread holds locks
	if (c == 0) {
        // Judging from the head of the CLH queue that no other thread is acquiring earlier than the current one
        // Setting state successfully based on CAS (expected old value of state is 0)
		if (!hasQueuedPredecessors() &&
			compareAndSetState(0, acquires)) {
            // Set the thread holding the lock to the current thread
			setExclusiveOwnerThread(current);
			return true;
		}
	}
    // The thread holding the lock is the current thread
	else if (current == getExclusiveOwnerThread()) {
        // Only in the current thread, single thread, without CAS update
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
    // Other threads already hold locks
	return false;
}

Acquisition(int) Method Implementation of AQS

public final void acquire(int arg) {
        // tryAcquire Checks Release for Success
        // Add Waiter builds CLH node objects and merges them into teams
        // acquireQueued thread blocking wait
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // acquireQueued returns true, representing that the thread is interrupted in the process of obtaining resources
        // This method is called to set the thread interrupt flag bit to true
		selfInterrupt();
}


final boolean acquireQueued(final Node node, int arg) {
    // Does the tag succeed in getting resources?
	boolean failed = true;
	try {
	    // Whether the marker has been interrupted during the waiting process
		boolean interrupted = false;
		// Cycling until resources are released
		for (;;) {
		    // Get the precursor node
			final Node p = node.predecessor();
			
			// If the predecessor is head, that is, this node is the second node, it is eligible to try to obtain resources.
			// It may be that the head releases the resource to wake up the node, or it may be interrupt()
			if (p == head && tryAcquire(arg)) {
			    // Successful access to resources
				setHead(node);
				// help GC
				p.next = null; 
				failed = false;
				return interrupted;
			}
			
			// Need to queue up to block waiting
			// If the thread interrupts in the process, no interrupt is responded to
			// And continue queuing for resources, set interrupted variable to true
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

(2) Non-blocking attempt acquisition

The implementation of tryLock() in ReentrantLock is only an unfair lock implementation. The implementation logic is basically consistent with tryAcquire. The difference is that there are no other threads waiting for the head of CLH queue through hasQueued Predecessors (), so that when resources are released, threaded requests for resources can be obtained first by queue insertion.

The implementation of tryLock() in ReentrantLock is as follows:

public boolean tryLock() {
	return sync.nonfairTryAcquire(1);
}

final boolean nonfairTryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	// No thread holds locks
	if (c == 0) {
	    // Setting state successfully based on CAS (expected old value of state is 0)
		// There is no check for threads waiting in the CLH queue
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	// The thread holding the lock is the current thread
	else if (current == getExclusiveOwnerThread()) {
	    // Only in the current thread, single thread, without CAS update
		int nextc = c + acquires;
		if (nextc < 0) // Overflow, integer overflow
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	// Other threads already hold locks
	return false;
}

(3) Blocking waiting for acquisition within a specified time

The basic realization is as follows:

  • 1. ReentrantLock's tryLock(long, TimeUnit) calls AQS's tryAcquireNanos(int, long) method
  • 2. AQS tryAcquireNanos first calls tryAcquire(int) to try to obtain, but not to call the doAcquireNanos(int, long) method.
  • 3. AQS doAcquireNanos judges whether the current node is head ed and whether resources can be obtained based on tryAcquire(int). If it cannot be obtained and the timeout time is longer than 1 microsecond, it will sleep for a period of time and then try to obtain it.

The implementation in ReentrantLock is as follows:

public boolean tryLock(long timeout, TimeUnit unit)
		throws InterruptedException {
	return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
		throws InterruptedException {
	// If the thread has been interrupted by the interrupt() method settings	
	if (Thread.interrupted())
		throw new InterruptedException();
	// First try try tryAcquire to get the lock	
	return tryAcquire(arg) ||
		doAcquireNanos(arg, nanosTimeout);
}

The implementation in AQS is as follows:

private boolean doAcquireNanos(int arg, long nanosTimeout)
		throws InterruptedException {
	if (nanosTimeout <= 0L)
		return false;
	// Deadline for access to resources	
	final long deadline = System.nanoTime() + nanosTimeout;
	final Node node = addWaiter(Node.EXCLUSIVE);
	// Does the tag succeed in getting resources?
	boolean failed = true;
	try {
		for (;;) {
		    // Get the precursor node
			final Node p = node.predecessor();
			// If the predecessor is head, that is, this node is the second node, it is eligible to try to obtain resources.
            // It may be that the head releases the resource to wake up the node, or it may be interrupt()
			if (p == head && tryAcquire(arg)) {
			    // Successful access to resources
				setHead(node);
				// help GC
				p.next = null; 
				failed = false;
				return true;
			}
			// Update the remaining timeout time
			nanosTimeout = deadline - System.nanoTime();
			if (nanosTimeout <= 0L)
				return false;
			// Does queuing require queuing congestion and waiting?	
			// If the timeout time is greater than 1 microsecond, the thread will sleep until the timeout time is up and try to retrieve it.
			if (shouldParkAfterFailedAcquire(p, node) &&
				nanosTimeout > spinForTimeoutThreshold)
				LockSupport.parkNanos(this, nanosTimeout);

			// If the thread has been interrupted by the interrupt() method settings
			// No longer queuing, just quit. 	
			if (Thread.interrupted())
				throw new InterruptedException();
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

(4) Response interrupt acquisition

ReentrantLock responds to interrupts and acquires locks by checking that the thread interrupt flag is true and actively throwing exceptions when the thread responds to interrupt wake-up in thead.interrupt() method during the sleep of park method. The core implementation is in the doAcquireInterruptibly(int) method of AQS.

The basic implementation is similar to blocking wait for acquisition, except that the acquire(int) method from AQS is invoked and the doAcquireInterruptibly(int) method from AQS is invoked instead.

private void doAcquireInterruptibly(int arg)
	throws InterruptedException {
	final Node node = addWaiter(Node.EXCLUSIVE);
	// Does the tag succeed in getting resources?
	boolean failed = true;
	try {
		for (;;) {
		    // Get the precursor node
			final Node p = node.predecessor();
			
			// If the predecessor is head, that is, this node is the second node, it is eligible to try to obtain resources.
			// It may be that the head releases the resource to wake up the node, or it may be interrupt()
			if (p == head && tryAcquire(arg)) {
			    // Successful access to resources
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return;
			}
			
			// Need to queue up to block waiting
			if (shouldParkAfterFailedAcquire(p, node) &&
			    // Wake up from queuing congestion if the check to interrupt flag is true
				parkAndCheckInterrupt())
				// Active response interruption
				throw new InterruptedException();
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

2 Explicit release

AQS resource sharing can be divided into exclusive and shared modes. This paper first introduces the explicit release of exclusive resources by taking ReentrantLock as an example. Shared mode will be introduced later.

Similar to explicit acquisition, the explicit release of ReentrantLock is basically achieved as follows:

  • 1. ReentrantLock implements the tryRelease(int) method of AQS, which reduces the state variable by 1. If the state becomes zero, it returns true if no thread holds the lock, otherwise it returns false.
  • 2. AQS release(int) method is based on tryRelease(int) queue whether there are any threads holding resources, if not, wake up the threads of the head node in CLH queue.
  • 3. The awakened thread continues to execute the logic in acquireQueued(Node,int) or doAcquireNanos(int, long) or doAcquireInterruptibly(int), and continues to attempt to obtain resources.

The tryRelease(int) method in ReentrantLock is implemented as follows:

protected final boolean tryRelease(int releases) {
	int c = getState() - releases;
	// Only the thread holding the lock is eligible to release the lock
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
		
	// Identify whether no thread holds locks	
	boolean free = false;
	
	// No thread holds locks
	// Re-entrant locks require an unlock for each lock
	if (c == 0) {
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}

The release(int) method in AQS is implemented as follows:

public final boolean release(int arg) {
    // Trying to release resources
	if (tryRelease(arg)) {
		Node h = head;
		// Header node is not empty
		// The status of the precursor node is updated to SIGNAL(-1) before the successor node enters the dormant state after joining the queue.
		// Head node state is 0, representing no successive waiting nodes
		if (h != null && h.waitStatus != 0)
		    // Wake up the second node
		    // Header node is the thread occupying resources, and the second node is the first thread waiting for resources.
			unparkSuccessor(h);
		return true;
	}
	return false;
}

3 reentrant

ReentrantLock, for example, is mainly implemented in the tryAcquire(int) method. If the thread holding the lock is the current thread, if so, update the synchronous state value and return true, which means that the lock can be acquired.

4 Sharable

Sharable resources Take ReentrantReadWriteLock as an example. The main difference between ReentrantLock and exclusive lock is that multiple threads are allowed to share read locks when they are acquired. When write locks are released, multiple blocked threads waiting for read locks can be acquired simultaneously.

The ReentrantReadWriteLock class defines the AQS state synchronization status value as read lock holder 16 bits high and write lock holder 16 bits low.

The logic of tryAcquireShared(int) and tryReleaseShared(int) implementations in Reentrant ReadWriteLock is longer. They mainly involve mutual exclusion between read and write, reentrant judgment, concessions between read and write locks. As the space is limited, they will not be expanded here.

The main implementation of ReadLock.lock()) is as follows:

  • 1. ReentrantReadWriteLock implements the tryAcquireShared(int) method of AQS to determine whether the current thread can acquire a read lock.
  • 2. AQS acquireShared(int) first attempts to obtain resources based on try acquireShared(int). If the acquisition fails, it joins the CLH queue to block and wait.
  • 3. ReentrantReadWriteLock's ReadLock.lock() Method Blocks AQS-based acquireShared(int) Method Waiting to Acquire Locks

The specific implementation of AQS sharing mode to acquire resources is as follows:

public final void acquireShared(int arg) {
    // tryAcquireShared returns a negative number to represent a failure to obtain shared resources
	// By entering the waiting queue, it does not return until resources are obtained.
	if (tryAcquireShared(arg) < 0)
		doAcquireShared(arg);
}

// The acquireQueued logic is basically the same as the acquireQueued logic described earlier.
// The difference is to change tryAcquire to tryAcquireShared
// There are also nodes on the CLH queue that will be propagated to wait for the resource after the resource has been acquired successfully.
private void doAcquireShared(int arg) {
	final Node node = addWaiter(Node.SHARED);
	 // Does the tag succeed in getting resources?
	boolean failed = true;
	try {
		boolean interrupted = false;
		for (;;) {
			final Node p = node.predecessor();
			if (p == head) {
				int r = tryAcquireShared(arg);
				// Resource Achievement Success
				if (r >= 0) {
 				    // Propagate to the node on the CLH queue waiting for the resource                             
					setHeadAndPropagate(node, r);
					p.next = null; // help GC
					if (interrupted)
						selfInterrupt();
					failed = false;
					return;
				}
			}
			// Need to queue up to block waiting
            // If the thread interrupts in the process, no interrupt is responded to
            // And continue queuing for resources, set interrupted variable to true
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

//  The resource is propagated to the node waiting for the resource on the CLH queue 
private void setHeadAndPropagate(Node node, int propagate) {
	Node h = head; 
	setHead(node);
	if (propagate > 0 || h == null || h.waitStatus < 0 ||
		(h = head) == null || h.waitStatus < 0) {
		Node s = node.next;
		if (s == null || s.isShared())
		    // Release shared resources
			doReleaseShared();
	}
}

Release read lock (ReadLock.unlock()) is mainly implemented as follows: Reentrant ReadWriteLock releases shared resources mainly as follows:

  • 1. ReentrantReadWriteLock implements the tryReleaseShared(int) method of AQS to determine whether there are threads holding read locks after the release of read locks.
  • 2. AQS release Shared (int) is based on tryRelease Shared (int) to determine whether dormant threads in the CLH queue are needed, and to execute doRelease Shared () if necessary.
  • 3. ReentrantReadWriteLock's ReeadLock. unlock () method releaseShared(int) method based on AQS to release locks

AQS sharing mode releases resources as follows:

public final boolean releaseShared(int arg) {
    // Allow sleeping threads to wake up in CLH
	if (tryReleaseShared(arg)) {
	    // Implementation resource release
		doReleaseShared();
		return true;
	}
	return false;
}
	
private void doReleaseShared() {
	for (;;) {
		Node h = head;
		if (h != null && h != tail) {
			int ws = h.waitStatus;
			// The current node is waiting for resources
			if (ws == Node.SIGNAL) {
			    // The current node is awakened by other threads
				if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
					continue;            
				unparkSuccessor(h);
			}
			// The condition for entering else is that the current node has just become the head node
			// The tail node has just joined the CLH queue and has not changed the state of the precursor node to SIGNAL before hibernation.
			// CAS failure is that the tail node has changed the state of the precursor node to SIGNAL before hibernation
			else if (ws == 0 &&
					 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
				continue;               
		}
		// After each wake-up of the backup node, the thread enters the doAcquireShared method and then updates the head
		// If the h variable is unchanged in this round robin, head = tail, and all nodes in the queue are awakened
		if (h == head)                 
			break;
	}
}

5. Fairness and Unfairness

This feature is relatively simple to implement. Take ReentrantLock lock as an example, fair lock directly acquires resources based on AQS (int), instead of fair lock trying to queue first: Based on CAS, expect state synchronization variable value to be 0 (no thread holds locks), update to 1, and queue again if all CAS updates fail.

// Fair Lock Implementation
final void lock() {
	acquire(1);
}

// Unfair Lock Implementation
final void lock() {
    // First CAS
    // The state value of 0 means that no thread holds a lock, and the lock is acquired directly by queuing.
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		acquire(1);
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

// CAS attempts to acquire locks again in the nonfairTryAcquire method
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // The second CAS attempt to acquire lock
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // Locks have been acquired
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

summary

The meaning of the state variable value of AQS does not necessarily represent resources. Different inheritance classes of AQS can define the state variable value differently.

For example, in the countDownLatch class, the value of the state variable represents the latch count that needs to be released (which can be understood as the number of latches that need to be opened). Each latch needs to be opened before the door can be opened. All waiting threads will start to execute. Each countDown() decreases the state variable by 1. If the state variable is reduced to 0, the dormant threads in the CLH queue will be awakened.

It is suggested to set a few questions and learn with problems when learning similar low-level source codes; before popular learning, it is suggested to understand the overall design thoroughly, the overall principle (you can read the relevant documents first), then study the details of source codes, so as to avoid getting into source codes at the beginning, which is easy to return to useless work.

Tags: Programming Java JDK

Posted on Wed, 09 Oct 2019 02:43:03 -0700 by namasteaz