Abstractqueuedsynchronizer (hereinafter referred to as AQS), Javadoc Description: Provides a framework for implementing blocking locks and related Synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues.
1. Provide a FIFO wait queue, using the method pseudo-code representation is:
Acquire:
if (! Get to Lock) {
Join queue
}
Release:
if (Release lock) {
Thread of Unlock waiting queue header node
}
2. Internally use the volatile int state to represent a synchronization status that can represent either the state of lock or the number of times a lock is used, for example, Semaphore uses the field to represent the number of licenses, Reentrantlock is used to indicate the number of reentrant times, and we can define our own status values to represent the thread's running state. Subclasses must implement serializable when inheriting Aqs;
3. Provide exclusive and share 2 sets of APIs, the general use is to maintain an internal class inheritance Aqs, to implement one of the APIs, to determine whether to obtain a lock. Reentrantlock uses the shared API that is used exclusively by Api,countdownlatch. The protected methods implemented by subclasses are:
Exclusive API to determine whether to get to the lock:
Tryacquire
Tryrelease
Share the API to determine if the lock is acquired:
Tryacquireshared
Tryreleaseshared
Isheldexclusively (This is a temporary matter)
4.AQS provides condition to implement Wait/notify function, into reentrantlock.newcondition ();
5.1.7 version Juc used to Aqs are: Reentrantlock/reentrantreadwritelock/semaphore.
Aqs inherits the Abstractownablesynchronizer class:
The thread that holds the lock in exclusive mode private transient thread exclusiveownerthread;protected final void Setexclusiveownerthread (thread t) { exclusiveownerthread = t;} Protected final Thread Getexclusiveownerthread () { return exclusiveownerthread;}
Queue definition for Aqs:
Private transient volatile node head;private transient volatile node tail;private static final unsafe unsafe = Unsafe.getu Nsafe ();p rivate static final long stateoffset;private static final long headoffset;private static final long TAILOFFSET;PR Ivate static final long waitstatusoffset;private static final long nextoffset;static {try {stateoffset = Unsaf E.objectfieldoffset (AbstractQueuedSynchronizer.class.getDeclaredField ("state")); Headoffset = Unsafe.objectfieldoffset (AbstractQueuedSynchronizer.class.getDeclaredField ("Head")); Tailoffset = Unsafe.objectfieldoffset (AbstractQueuedSynchronizer.class.getDeclaredField ("tail")); Waitstatusoffset = Unsafe.objectfieldoffset (Node.class.getDeclaredField ("Waitstatus")); Nextoffset = Unsafe.objectfieldoffset (Node.class.getDeclaredField ("Next")); } catch (Exception ex) {throw new Error (ex);}}By setting the head/tail/state/waitstatus of the queue and the next value of the node through unsafe, we can see that the approximate structure of the queue is:
Look at the specific definition of the queue node:
Static final class Node {//Tag node type is shared or exclusive static final node shared = new node (); static final Node EXCLUSIVE = null;//The following 4 are nodes State value static final int CANCELLED = 1;static final int SIGNAL = -1;static final int CONDITION = -2;static final int Propag ATE = -3;/** node state, corresponding to the above several status values: 0:normal Status1: The node is canceled, the node running cancelled state will be cleaned out-1: Need to wake the next node of the current node-2: In the case of Newcondition, Condition also maintains another condition queue-3: Shared mode, which represents the other nodes that need to pass release to the queue */volatile int waitstatus; Volatile Node prev; Next is null, which does not mean that the node being changed is a tail node because it is the volatile node next of the pre and next when it joins the queue; volatile thread thread; Exclusive mode, point to the next node of the conditional queue, or the shared node Nextwaiter value in share mode; Final Boolean isShared () {return nextwaiter = = SHARED; } final node predecessor () throws NullPointerException {Node p = prev; if (p = = null) throw new NullPointerException (); else return p; } node () {//used to establish initial head or SHARED marker} node (thread thread, Node mode) { Used by Addwaiter this.nextwaiter = mode; This.thread = thread; } Node (thread thread, int waitstatus) {//used by Condition this.waitstatus = Waitstatus; This.thread = thread; }}
I. Acquire and release under exclusive mode
Acquire:
Acquire that do not respond to interrupts
Public final void acquire (int arg) { if (!tryacquire (ARG) && acquirequeued (Addwaiter (node.exclusive), ARG)) selfinterrupt ();////Suspend wake-up return the interrupt state is true, this will break the current thread }
Subclass implementation Tryacquire,aqs does not provide
protected Boolean tryacquire (int arg) { throw new unsupportedoperationexception (); }
If not, then Addwaiter joins the wait queue and suspends the thread:
Private node Addwaiter (node mode) {//Initialize a node nodes = new node (thread.currentthread (), mode); Try the fast path of Enq; Backup to full Enq on Failure node pred = tail;//First try to join directly behind the tail node,//From here can also be seen, first point node's pre to the tail node, and then CAs set tail, The next point of the original tail is pointed to the node,//So maybe next is empty, but the pre of the joined node must be present if (pred! = null) { Node.prev = pred; if (Compareandsettail (pred, node)) { pred.next = node; return node; } } If it fails, the For loop loops joins Enq (node); return node;}
Look at the Enq operation:
Private node Enq (final node node) {//loop operation, tail does not exist will initialize an empty nodes, and the head and tail point to the empty node,//And then CAs joins node to ensure that the node is bound to join for (;;) { Node t = tail; if (t = = null) {//Must initialize if (Compareandsethead (new Node ())) tail = head; } else { Node.prev = t ; if (Compareandsettail (t, node)) { t.next = node; return t;}}}
After joining the node to the wait queue, try suspending the thread:
Final Boolean acquirequeued (final node node, int arg) {Boolean failed = true; try {Boolean interrupted = false; for (;;) {//The new Pre node to join the endpoint final node P = node.predecessor (); If the pre node is a header, retry acquire again, and if successful, set node as the Head node//Note that the head node represents the node holding the lock if (p = = Head && ; Tryacquire (Arg)) {Sethead (node); P.next = null; Help GC failed = false; return interrupted; }//If the pre is not a head node or acquire fails, try suspending if (Shouldparkafterfailedacquire (p, node) && p Arkandcheckinterrupt ()) interrupted = true; }} finally {//If the above operation has an exception, you need to have node if (failed) cancelacquire (node); }}/** Set head node */private void Sethead (node node) {head = node; Node.thread = null; Node.prev = null;} /** Check whether it is necessary to suspend this method is to set the new join node of the pre node waitstatus to signal (positive success), so that the pre node release when the decision is notNeed to wake the next node */private static Boolean Shouldparkafterfailedacquire (node pred, node node) {int ws = Pred.waitstatus; if (ws = = node.signal)/* * This Node have already set status asking a release * to SIGNAL it, so it Can safely park. */return true; if (ws > 0) {/* * will filter the nodes of the cancelled state during setup, remove the nodes of the cancelled state */do {Node.prev = Pred = Pred.prev; } while (Pred.waitstatus > 0); Pred.next = node; } else {/* * Waitstatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don ' t park yet. Caller would need to * retry to make sure it cannot acquire before parking. */Compareandsetwaitstatus (pred, WS, node.signal); } return false;} /** call Locksupport.park Blocking thread */private final Boolean parkandcheckinterrupt () {//Suspend thread Locksupport.park (this);// When the pre node is release, the check status is signal to wake the current node, which returns the interrupt status of the thread return thread.interrupted ();} /**aCquire and suspend process exception, need to cancel acquire*/private void Cancelacquire (node node) {//NULL to return directly if (node = = null) return; Node.thread = null; The next step is to skip the pre-cancelled node and point the pre to queue node before the first non-canceling state node nodes pred = Node.prev; while (Pred.waitstatus > 0) node.prev = pred = Pred.prev; Prednext is the next node of the first non-canceled state node in front of the queue node Prednext = Pred.next; Can use unconditional write instead of CAS here. After this atomic step, and other Nodes can skip past us. Before, we is free of interference from the other threads. Node.waitstatus = node.cancelled; Check the location of the node nodes below, if it is the tail node, set pred directly to the tail node,//And then set the previous pred's next to null if (node = = Tail && compareandsettail (node , pred)) {Compareandsetnext (pred, prednext, NULL); } else {//not tail node int ws; Here, it is judged that the pre of node that is processed above is the head node//not the head node is going to CAs to ensure that its state is signal if (pred! = head && (ws = Pre d.waitstatus) = = Node.signal | | (WS <= 0 && Compareandsetwaitstatus (pred, WS, Node.signal))) && Pred.thread! = null) {//node Next is not NULL And the state is not a cancel state, the next node of the node is associated to the Pred node next to the Node.next; if (next! = null && next.waitstatus <= 0) compareandsetnext (pred, Prednext, next); } else {//If node's pre is a header node, it needs to wake node's next node unparksuccessor (node); }//next points to himself node.next = node; Help GC}}private void Unparksuccessor (node node) {/* * * If status is negative (i.e., possibly needing signal ) Try * To clear in anticipation of signalling. It is OK if the this * fails or if status are changed by waiting thread. */int ws = Node.waitstatus; if (WS < 0) compareandsetwaitstatus (node, WS, 0); /* * * * Before said Addwaiter is first pre->tail->next, so there is tail has changed but next has not changed the situation * here will look forward from the tail will not NULL, and the state is not a canceled node */ 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; }//found on the Unpark, but Unpark is not necessarily acquire successful, acquire over there will always loop if (s! = null) Locksupport.unpark (s.thread);}
Next look at the acquireinterruptibly method of the response interrupt, here will first determine whether the thread is interrupted, the interrupt will throw an exception directly, no interruption and then try to request
Public final void acquireinterruptibly (int arg) throws interruptedexception { if (thread.interrupted ()) throw new Interruptedexception (); if (!tryacquire (ARG)) doacquireinterruptibly (ARG);}
The difference between the Doacquireinterruptibly method and the previous one is that it throws an exception directly after the thread breaks, not the return interrupt state to the previous layer as before
private void doacquireinterruptibly (int arg) throws interruptedexception { final node node = addwaiter ( node.exclusive); Boolean failed = true; try {for (;;) { final Node p = node.predecessor (); if (p = = head && tryacquire (ARG)) { Sethead (node); P.next = null; Help GC failed = false; return; } if (Shouldparkafterfailedacquire (p, node) && parkandcheckinterrupt ()) //difference throw new Interruptedexception (); } } Finally { if (failed) cancelacquire (node); }}
Support for interrupts and timeout periods
Public final Boolean Tryacquirenanos (int arg, long nanostimeout) throws interruptedexception { if ( Thread.interrupted ()) throw new Interruptedexception (); return Tryacquire (ARG) | | Doacquirenanos (ARG, nanostimeout);}
Private Boolean Doacquirenanos (int arg, long nanostimeout) throws Interruptedexception {//Take one time long lasttime = System.nanotime (); Final node node = addwaiter (node.exclusive); Boolean failed = true; try {for (;;) {final Node P = node.predecessor (); if (p = = head && tryacquire (ARG)) {Sethead (node); P.next = null; Help GC failed = false; return true; }//time-out less than 0 returns False if (Nanostimeout <= 0) return false; Here Spinfortimeoutthreshold for static final long spinfortimeoutthreshold = 1000L; If the timeout time is greater than Spinfortimeoutthreshold,park, then the direct spin if (Shouldparkafterfailedacquire (p, node) && Nanostimeout > Spinfortimeoutthreshold)//Bottom call Unsafe.park (false,nanostimeout) Lock Support.parknanos (this, nanostimeout);//Wake Up and recalculate the time Long now = System.nanotime (); Nanostimeout-= Now-lasttime; Lasttime = Now; If the thread breaks, throw the exception directly if (thread.interrupted ()) throw new Interruptedexception (); }} finally {if (failed) cancelacquire (node); }}
Responses to interrupts and response times are acquire similar to acquire.
Release
<span style= "FONT-SIZE:18PX;" >public Final Boolean release (int arg) {//tryrelease can be freed by the subclass implementation to determine if (Tryrelease (ARG)) { Node h = head; if (h! = NULL && H.waitstatus! = 0) unparksuccessor (h); return true; } return false;} </span>
Unparksuccessor The first non-canceled state node of the Unpark queue has been mentioned above.
The approximate process is:
Reference:
http://brokendreams.iteye.com/blog/2250372
http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer#anch132323
Juc Source Analysis 6-locks-aqs-Exclusive mode