Here, I will share some learning materials on the principles and source code of abstractqueuedsynchronizer. The core idea is simple. I suggest you search for AQS papers on the Internet and read the papers first, then, let's look at the source code in the Java concurrents package, which will be more thorough in understanding.
I. Core Ideas:
1. Atomic operations in synchronization status
2. Thread blocking and Wakeup
3. Maintain a queue. Both inbound and outbound queues are atomic operations.
Ii. Basic operations:
1. Inbound queue:
A) Add to the chlorophyll queue, set the singal status of its predecessor node, and block
2. output queue:
A) the head node of the queue can obtain the execution permission.
Iii. Design Ideas for unfair lock acquisition:
Nonfirelock
if(!tryAcquire){ node = create a new node and add to the chl queue pre = node’s precedueer while(pre != head || !tryAcquire(args)){ if(pre’s signal bit is true){ park this thread;} else { set the pre’s singal bit is true} pre = node’s precedueer} head = node;}
4. release locks
Free the lock
if(tryRelease(args) && head node’s singal is set){ compare and set its singal to false; unpark its successor if exsits;}
V. Overall Design Philosophy: The template method is a perfect embodiment and its main logic has been implemented, for example: the tryacquire () method is assigned to its subclass to implement the addition of wait nodes, insert nodes by atomic operations, block queues by nodes, and check before blocking. This achieves high scalability.
After the reentrantlock object calls the lock () method
1. Call different lock () Methods Based on the lock nature (fairness and non-Fairness)
A) Fair lock: directly call the acquire (1) method of abstractqueuedsynchronizer
B) Unfair lock: first compete for the lock. If the lock is obtained, set exclusiveownerthread to 1 for the current thread and abstractqueuedsynchronizer; otherwise, request the acquire (1) method of abstractqueuedsynchronizer.
2. The acquire (INT ARGs) method of abstractqueuedsynchronizer is divided into the following steps:
A) try to obtain the lock and call the specific Boolean tryacquire (ARGs) method of the subclass.
I. Fair lock: first judge the state? 0 (check whether the thread at the header node is the current thread. If yes, the lock is obtained. Set the thread to have a mutex lock, state = ARGs, and return true) (judge whether the thread that owns the mutex lock is the current thread. If yes, re-enter the thread, State ++, and return true); finally, false is returned.
Ii. Unfair lock: State? 0 (judge whether the thread with mutex lock is the current thread, yes, re-import lock, State ++, return true) :( false)
B) if true is returned in the previous step, the thread is successfully locked; otherwise, it should be added to the lock Queue (clh improves the queue) and blocked until it is awakened.
I. the addwaiter (node) method of abstractqueuedsynchronizer adds a new node to the end of the lock queue and returns this node: First, according to the node. exclusive and the current thread construct a node T. If the tail node P is non-null, add the new node to the end of the team T. PRED = P; comparseandsettail (P, T) is successful, then t. PRED = P; P is returned; otherwise (P is controlled together with null), The Enq (node) method is called, the atomic node is added to the end of the team, and T is returned, then determine whether the tail T is empty,
A) if it is null, head H; H. Next = node; node. Pred = H; compareandsethead (H) is created successfully, tail = node; return h;
B) It is not null. If T. Next = node; compareandsettail (T, node) is successful, node. Pred = T, and T is returned.
C) if no return is returned, an infinite loop is made until return
Ii. boolean queueacquire (node, argS ):
-
-
- First determine whether P = Head & tryacquire (ARGs) is true, yes: Then sethead (P. next) {head = P. next; head. PRED = NULL; head. thread = NULL}, setexclusiveownerthread (thread. currentthread (); setsate (ARGs );
- Otherwise, set the status bit signal of the PRED node P and then block the thread.
A) change the status bit shouldparkafterfailedacquire (p, node): determines if S = P. waitstatus is <0, and returns true directly;
B) S> 0; indicates that the P node has been canceled. PRED = P. PRED until p. waitstatus> 0; S = 0; atomic compareandsetwaitstatus (p, 0, node. signal),; returns false
-
-
-
-
- Use Boolean parkfailedacquire () to block the thread: locksupport. Park (thread. currentthread (); Return thread. interruptted (used to determine whether the thread is interrupted due to cancellation );
- After being awakened, execute interruptted = true
- Infinite loop until return interruptted;
Iii. If thread! Tryacquire (ARGs) & queueacquire (node, argS) indicates that the thread is awakened by the interrupted operation, and selfinterrupt () {thread. currentthread (). Interrupt ()}
After the unlock () method is called, the release (1) and tryrelease (INT) of abstractqueuedsynchronizer are executed)
1. First, release (INT) should determine whether the lock Boolean tryrelease (INT) can be released)
A) First, reduce the number of retries of the lock. c = getstate ()-ARGs; if C = 0 is true, setexclusiveownerthread (null); setstate (c); true is returned, otherwise, setstate (c); Return false;
B) if the lock is successfully released, determine the head! = NULL & head. waitstatus! = 0 is true or not. If yes, wake up the successor unparkpreducesser (head );
I. First (Ws = head. waitstatus) <0: compareandsetwaitstatus (Head, WS, 0 );
Ii. ws> 0: indicates that the dummy node is canceled. Locate the node from tail until the head, and assign the node waitstatus <0 to next.
Iii. locksupport. unpark (next. Thread );
Conditionobject indicates the Java-style await () and signal:
There is a one-way waiting queue inside it.
When the await () method is called:
1. Check whether the thread is terminated. If yes, interrupttedexception is thrown.
2. node S = addcondtionwaiter (); add a new node to the waiting queue
A) First, determine firstwaiter. waitstatus = node. cancelled. If yes, the header node is canceled. Call the unlinkcancelledwaiter () method to remove the cancel node from the queue.
I. traverse one-way queues and judge the node's waitstatus in sequence
B) Otherwise, construct a node S = new node (thread. currentthread (), node. condition); If the header node is null, firstwaiter = s; otherwise, firstwaiter. next = s; lastwaiter = s; return S;
3. Release the lock int savestate = fullyrelease (); call release (getstate ());
4. cyclically determine whether the node is in the lock queue! Issyncqueue (s ):
A) if S. waitstatus = node. condition | S. Pred = NULL, this node is not in the lock queue, return false;
B) if S. Pred! = NULL; return true;
C) Finally, search from tail to head, and check whether node. Thread = thread. currentthread (); If yes, return true; otherwise, return false;
5. If it is not in the sync queue, locksupport. Park (thread. currentthread (); If the thread is awakened, if (interruptmode = checkinterruptwhilewaiting (s )! = 0) break;
A) thread. interruptted? (Transforcancelledwaiter (s )? Throw_ie: reinterrupt): 0 determines the subsequent operations based on whether the thread is interrupted.
B) transforcancelledwaiter (s ):
I. if compareandsetstatus (p, node. condition, 0) Success indicates that the cancellation operation is performed before the wake-up operation, the thread needs to be locked again and an IE exception is thrown. Therefore, Enq (s); Return throw_ie;
Ii. Otherwise, it indicates that the cancel Operation is triggered, while (! Issyncqueue () thread. Yield (); If the node has not entered the lock queue, wait, return reinterrupt;
6. If (queueacquire (S, savestate) & interruptmode! = Throw_ie) interruptmode = reinterrupt; if the thread is interrupted, queueacquire () returns false;
7. Judge S. nextwaiter! = NULL is true, then unlinkcancelledwaiter ();
8. If interruptmode! = 0; Different interruptmode processes reportinterrupt (interruptmode );
A) throw an exception or selfinterrupt ();
Conditional signal (): Move firstwaiter into lock queue
1. Determine firstwaiter! = NULL; dosignal (firstwaiter );
2. dosignal (firstwaiter): If tranferforsignal (first) fails, move the next node until the next node is null.
3. boolean tranferforsignal (first) must be determined based on the cancellation operation
A )! Compareandsetstatus (first, node. conditon, 0); it indicates that the waitstatus of this node has been set to cancelled, so it cannot be moved into the lock queue and return false;
B) otherwise node P = Enq (first); If P. waitstatus> 0 |! Compareandsetstatus (p, 0, node. signal) is set up, indicating that the insertion of the lock queue fails to wake up the thread locksupport of this node. unpark (first. thread); then, the method await () will execute to further determine that issyncqueue () is true, and then call queueacquire () to obtain the lock