AbstractQueuedSynchronizer is based on a FIFO two-way Chain queue==CLH queue==, a class used to build locks or synchronizers, also known as Java synchronizers. The equitable and unfair locks of ReentrantLock consist of the synchronizers, and the chain queue structure diagram is as follows.
You can understand that bank ATM machine withdraws money, one person goes to get the lock first, during which time other threads are blocked. Only when he finishes collecting money, he goes away and releases the lock. Only the people behind him can get the released lock and withdraw money.
The synchronizer uses an int value to represent the state by ==inheriting the synchronizer using an internal class==implementing methods such as tryRelease and tryAcquire to manage the state, using the following three methods:
- getState() Gets the state
- setState() Basic setup state
- compareAndSetSate(int,int) atomicity setting state based on CAS implementation
AQS Node
Nodes contain the following states:
- CANCELLED, with a value of 1, indicates that the current thread has been cancelled;
- SIGNAL, with a value of -1, indicates that the successor nodes of the current node contain threads that need to be run, that is, unpark;
- CONDITION, with a value of -2, indicates that the current node is waiting for condition, that is, in the condition queue;
- PROPAGATE, with a value of -3, indicates that subsequent acquireShared can be executed in the current scenario;
- A value of 0 indicates that the current node is in the sync queue waiting to acquire a lock.
Node Additional Information:
Node prev | Precursor Node |
---|---|
Node next | Successor Node |
Node nextWaiter | Store successor nodes in condition queue |
Thread thread | Current thread on queue entry |
exclusive lock
Locks can only be held by one thread lock at a point in time. AQS implements ReentrantLock, which is divided into fair locks and unfair locks.
- fair lock
Ensures the order in which threads acquire locks under multiple threads, while first-come threads acquire locks first
- Unfair Lock
When locking, try to acquire locks without regard to queue waiting, instead of automatically waiting at the end of the queue
Shared Lock
Locks can be acquired simultaneously by multiple threads at a point in time, AQS implements CountDownLatch, ReadWriteLock
1. AQS implements ReentrantLock unfair lock
ReentrantLock Unfair Lock Acquisition Lock
1,lock()
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
2,acquire()
Gets the new state by executing the compareAndSetState method
public final void acquire(int arg) { //If tryAcquire succeeds, acquire ends; if (!tryAcquire(arg) && //The AcquireQueued method blocks until a lock is acquired //addWaiter adds the current thread to the end of the queue acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //Interrupt the current thread if tryAcquire fails and acquiredQueued succeeds selfInterrupt(); }
- Attempting to acquire a lock;
- If not, construct the current thread as Node and join the queue;
The addWaiter method queues nodes, each thread being a node Node, forming a two-way queue, similar to a CLH queue. - Try fetching again, if you don't get the one that pulls the current thread off the thread scheduler and goes into a wait state.
3,tryAcquire()
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //Get Status int c = getState(); //If the lock is not occupied by any thread, it can be acquired by the current thread if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //If fetched to see if it is being checked by the current thread else if (current == getExclusiveOwnerThread()) { //Get it again if it is the current thread, count + 1 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
The Synchronizer-provided method of state operation is used in the tryAcquire method, and CAS principle ensures that only one thread can successfully modify the state, while threads that have not been successfully modified are queued.
4,acquireQueued()
The AcquireQueued method blocks until a lock is acquired
//node is null, exclusive blocking wait final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //p is the previous node of the current node final Node p = node.predecessor(); //Set the current line node as the head node if p is the head node and succeeds if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //interrputr is true if the thread needs to be blocked if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
- Gets the precursor node of the current node;
- The current drive node is the head node and can acquire the state, which means that the current node holds the lock.
- Otherwise, enter the waiting state.
5,shouldParkAfterFailedAcquire()
Determine if a thread needs to be blocked
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; //Threads need to run if (ws == Node.SIGNAL) return true; //Ws>0 Thread in CANCEL State if (ws > 0) { //Clear these threads from the queue do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //Set Waiting to Running State compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
- First determine if the thread is running
- Clear them all from the queue if they are in CANCEL state
- Set the current node's next node waiting state to ready to run
6,parkAndCheckInterrupt()
// The park method leaves other threads waiting private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
- Call blocking thread local method park
- Returns whether the thread is blocked
ReentrantLock Unfair Lock Release Lock
1,unlock()
Call release to release a lock
public void unlock() { //Release a lock sync.release(1); }
2,release()
Release lock
public final boolean release(int arg) { //Call tryRelease to attempt to release a lock if (tryRelease(arg)) { Node h = head; //After successful release if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
- Calling the tryRelease method to attempt to release the lock failed to return false
- Release success determines that the header node is not null and the state is not waiting to acquire the lock state
- Wake up the thread if the condition is met and return true
3,tryRelease()
Attempt to release the current lock
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
- Release lock must be the current thread, if not the current thread threw an exception
- If the thread that acquires the lock is the current thread, it determines whether the state after release is 0, that is, the CANCEL state before release. If so, it sets the exclusive mode thread to null and sets the state to 0, returning true.
4,unparkSuccessor()
Wake up the thread after the lock is released to compete for CPU resources together
private void unparkSuccessor(Node node) { //Get the current node state int ws = node.waitStatus; //Set state to wait for lock to be acquired if (ws < 0) compareAndSetWaitStatus(node, ws, 0); //If there are no next nodes or if the next node state is CANCEL, clear them Node s = node.next; 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; } //Otherwise, call the unpark method in LockSupport to wake up the next node if (s != null) LockSupport.unpark(s.thread); }
- Get the current node state
- Judges that if the state is <0, that is, not waiting to acquire the lock state, the state is set to waiting to acquire the lock state
- If there are no next nodes or if the next node state is CANCEL, clear them
- When the next node of the current node is not null, call the unpark method in LockSupport to wake up the next node
5,unpark()
Call UNSAFE's local method unpark to wake up a thread
public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
2. AQS Implements ReentrantLock Fair Lock
The biggest difference between AQS implementing ReentrantLock fair locks and unfair locks is the following code:
The source code is as follows:
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
=== Determine if the Current Thread is at the top of the CLH queue to achieve fairness==.
3. Custom lock implemented by AQS
public class MyAQSLock implements Lock { private final Sync sync; public MyAQSLock() { sync = new Sync(); } @Override public void lock() { sync.acquire(1); } @Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } @Override public boolean tryLock() { return sync.tryAcquire(1); } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1,unit.toNanos(time)); } @Override public void unlock() { sync.release(1); } @Override public Condition newCondition() { return sync.newCondition(); } /** * Build lock, unlock implementations into internal classes using AQS */ private class Sync extends AbstractQueuedSynchronizer{ Condition newCondition(){ return new ConditionObject(); } @Override protected boolean tryAcquire(int arg) { //The first thread came in to get the lock int state = getState(); //Used for reentrant lock judgment Thread current = Thread.currentThread(); if(state==0){ if(compareAndSetState(0,arg)){ setExclusiveOwnerThread(Thread.currentThread()); return true; } } //Re-enter Lock Judges that the current thread is the same as the exclusive lock thread and retrieves it again else if(current==getExclusiveOwnerThread()){ int next = state+arg; if(next<0){ throw new RuntimeException(); } setState(next); return true; } return false; } /** * Reentrant release lock * @param arg * @return */ @Override protected boolean tryRelease(int arg) { if(Thread.currentThread()!=getExclusiveOwnerThread()){ throw new RuntimeException(); } int state = getState()-arg; if(state==0){ setExclusiveOwnerThread(null); setState(0); return true; } setState(0); return false; } } }
Test (Re-Lockable)
public class TestAQSLock2 { MyAQSLock myLock = new MyAQSLock(); private int value; private int value2; public int a(){ myLock.lock(); try { b(); return value++; }finally { myLock.unlock(); } } public void b(){ myLock.lock(); try { System.out.println(++value2); }finally { myLock.unlock(); } } public static void main(String[] args) { TestAQSLock2 myLock = new TestAQSLock2(); for(int i=0;i<50;i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":" + myLock.a()); }).start(); } } }