The previous article mentions that Aqs is based on CLH lock queue
, then what is the CLH lock queue, which says complex is complex and simple and simple, so-called Boulevard to Jane:
CLH lock queue其实就是一个FIFO的队列,队列中的每个结点(线程)只要等待其前继释放锁就可以了。
AbstractQueuedSynchronizer
is Node
to implement a variant of the CLH lock queue through an internal class, but the rationale is similar.
Node
before we introduce the class, we introduce Spin Lock
the following, which is usually used CLH lock queue
to implement the spin lock, so the so-called spin lock simply means that the thread waits through the loop rather than sleep. Talk more than show code:
classClhspinlock {Private FinalThreadlocal<node>prev; Private FinalThreadlocal<node>node; Private Finalatomicreference<node> tail =NewAtomicreference<node> (NewNode ()); PublicClhspinlock () { This. Node =NewThreadlocal<node>() { protectedNode InitialValue () {return NewNode (); } }; This. prev =NewThreadlocal<node>() { protectedNode InitialValue () {return NULL; } }; } Public voidLock () {FinalNode node = This. Node.get (); Node.locked=true; //A CAS operation joins the node corresponding to the current thread into the queue,//and at the same time get the reference of the former node, then wait for the pre-release lockNode pred = This. Tail.getandset (node); This. Prev.set (pred); while(pred.locked) {//Enter Spin } } Public voidunlock () {FinalNode node = This. Node.get (); Node.locked=false; This. Node.set ( This. Prev.get ()); } Private Static classNode {Private volatile Booleanlocked; }}
The thread in the above code cleverly passes a ThreadLocal
reference to the current node and the previous node, and the spin is the while loop in lock. Overall the benefit of this implementation is to ensure fair competition for all waiting threads, and not compete for the same variable, as each thread waits for its own pre-release. The advantage of spin is that the thread does not need to sleep and wake, reducing the overhead of system calls.
Public Static voidMain (string[] args) {FinalClhspinlock lock =NewClhspinlock (); Lock.lock (); for(inti = 0; I < 10; i++) { NewThread (NewRunnable () {@Override Public voidrun () {lock.lock (); System.out.println (Thread.CurrentThread (). GetId ()+ "acquired the lock!"); Lock.unlock (); }}). Start (); Thread.Sleep (100); } System.out.println ("Main thread unlock!"); Lock.unlock ();}
The result of the above code should be exactly the same as in the previous article.
ClhSpinLock
The node class implementation is simply a Boolean value, AbstractQueuedSynchronizer$Node
the implementation is slightly more complex, presumably this:
+------+ prev +-----+ +-----+| | <----| | <----| | Tail +------+ +-----+ +-----+
- Head: First pointer
- Tail: Tail Hands
- Prev: Refers to the forward pointer
- Next: This pointer is not drawn in the graph, it is opposite prev, pointing to the successor
The key difference is the next pointer, this is because the AQS thread is not always spinning, and may be repeated sleep and wake, which requires the previous release of the lock through the next pointer to find its successor to wake up, that is, the Aqs waiting queue is followed by the previous wake. The AQS combines the advantages of both spin and sleep/wake methods.
Where the thread sleeps and wakes up is what I'm going to talk about in my next article LockSupport
.
Finally, one of the key points in the above ClhSpinLock
class is lock
where the comment is in the method:
一个CAS操作即可将当前线程对应的节点加入到队列中,并获取到其前继。
It can actually be said that the whole Aqs framework is based on CAS, these atomic operations are the core of multi-threaded competition, Aqs many of the code around to reduce competition. I will be in the back of the AbstractQueuedSynchronizer
source analysis to do a detailed introduction.
Java AQS Framework for source code Learning (ii) CLH lock queue and spin lock