Java multi-thread---using countdownlatch to illustrate the principle of AQS implementation

Source: Internet
Author: User

This article is based on JDK 1.8.

Use of Countdownlatch

The previous article described volatile and the use of volatile to achieve spin locks, such as the tool class under the Java.util.concurrent.atomic package. However, volatile usage scenarios are limited, and in many cases they are not, and need to be synchronized or various locks implemented. Today, let's take a look at the implementation of several locks.

Let's take a look at one of the simplest ways to use countdownlatch, an example of which is simple and can be run to see the effect. The role of Countdownlatch is to start execution when one thread needs another or more threads to complete. For example, the main thread to wait for a sub-thread to complete the environment-related configuration loading work, the main line friend continue to execute, you can use the Countdownlatch to achieve.

For example, the following example, first instantiate a countdownlatch, the parameter can be interpreted as a counter, here is 1, then the main thread executes, invokes the worker child thread, and then calls the Countdownlatch await () method, which indicates that the main thread is blocked. When the child thread executes, the Finnaly block calls the countdown () method, which indicates that a wait has been completed and the counter is reduced by one until it is reduced to 0, and the main thread starts executing again.

Private StaticCountdownlatch latch =NewCountdownlatch (1); Public Static void Main(string[] args)throwsinterruptedexception{System. out.println("Main thread starts ..."); Thread thread =NewThread (New Worker()); Thread.Start(); System. out.println("Main thread waiting ..."); System. out.println(Latch.toString()); Latch.await(); System. out.println(Latch.toString()); System. out.println("Main thread continues ..."); } Public Static classWorkerImplementsRunnable {@Override         Public void Run() {System. out.println("Child thread task is executing");Try{Thread.Sleep( -); }Catch(Interruptedexception e) {            }finally{latch.Countdown(); }        }    }

The results of the implementation are as follows:

主线程开始......子线程任务正在执行主线程等待......[email protected][Count = 1][email protected][Count = 0]主线程继续.......
the principle of AQS

So how to use the function is how to achieve, the following is to say that the core technology to achieve its principle AQS. The AQS full name AbstractQueuedSynchronizer is an efficient and extensible synchronization mechanism provided in the java.util.concurrent. It can be used to implement a synchronizer that can depend on the int state, get and release parameters, and an internal FIFO wait queue, except, for, and CountDownLatch ReentrantLock so on, the Semaphore feature implementation uses it.

Next, use Countdownlatch to analyze the implementation of AQS. Suggested to see the article when the first general look at the source code, to help understand the following said content.

In our method called awit() and countDown() when there are several key call relationships that have occurred, I drew a method call graph.

First, a Sync inner class is defined inside the Countdownlatch class, and the inner class is inherited from Abstractqueuedsynchronizer. And overrides the method tryAcquireShared and tryReleaseShared . For example, when calling a method, Countdownlatch invokes the method of awit() the internal class sync, acquireSharedInterruptibly() and then calls the method in this method tryAcquireShared , which is the Countdownlatch's internal class sync rewrite Abstractqueu Method of Edsynchronizer. Invoke the same countDown() method.

This approach is a standardized way to use Abstractqueuedsynchronizer, broadly divided into two steps:

1. Internal hold the object Sync from Abstractqueuedsynchronizer;

2. and rewrite some or all of the Abstractqueuedsynchronizer protected methods in Sync, including the following:

Subclasses are required to override these methods in order for the user (the user here to refer to Countdownlatch, etc.) to be able to add their own judgment logic, such as Countdownlatch tryAcquireShared added a judgment to determine whether the state is not 0, if not 0, To meet the invocation criteria.

tryAcquireAnd tryRelease is the corresponding, the former is the exclusive mode of acquisition, the latter is exclusive mode release.

tryAcquireSharedAnd tryReleaseShared is the corresponding, the former is the shared mode gets, the latter is the shared mode release.

We see the Countdownlatch overriding method tryacquireshared implemented as follows:

protectedinttryAcquireShared(int acquires) {            return (getState01 : -1;        }

Determines whether the state value is 0, 0 returns 1, otherwise returns-1. The state value is a volatile variable in the Abstractqueuedsynchronizer class.

privatevolatileint state;

In Countdownlatch, the state value is the counter, and when the await method is called, the value is assigned to state.

waiting for thread to queue

According to the above logic, the call to await () method, first to get the value of state, when the counter is not 0, it is necessary to wait for the thread to run, call the Doacquiresharedinterruptibly method, The first action to come in is to try to join the wait queue, called the Addwaiter () method, the source code is as follows:

Came here to the core of AQS, AQS with a Node class inside the maintenance of one? CHL Node FIFO? queue. Joins the current thread to the wait queue and implements blocking of the current thread through the Parkandcheckinterrupt () method. The following is a large part of the implementation of the description of the CHL queue, the use of CAS to achieve queue access will not be blocked.

Private void doacquiresharedinterruptibly(intArgthrowsinterruptedexception {//Join the wait queue        FinalNode node =Addwaiter(Node.SHARED);BooleanFailed =true;//Enter CAS cycle        Try{ for(;;) {//When a node (associated with a thread) enters the wait queue, gets the prev node of this node                FinalNode p = node.predecessor();//If the acquired Prev is head, which is the first waiting thread in the queue                if(p = = head) {//Try again to apply the reaction to Countdownlatch is to see if the thread still needs to wait (state is 0)                    intR =tryacquireshared(ARG);//If R >=0 indicates that no threads need to wait for state==0                    if(R >=0) {//try to set the node associated with the first thread to head                        setheadandpropagate(node, r); P.Next=NULL;//help GCFailed =false;return; }                }//After the spin tryacquireshared, state is not 0, will be here, the first time, Waitstatus is 0, then node Waitstatus will be set to signal, the second time to come here, will use the Locksupport Park method to block the current thread.                if(Shouldparkafterfailedacquire(p, node) &&Parkandcheckinterrupt())Throw NewInterruptedexception (); }        }finally{if(failed)Cancelacquire(node); }    }

I see that the above first executed the Addwaiter () method, is to join the current thread waiting queue, the source code is as follows:

/** Marker to indicate a node was waiting in shared mode * / Static FinalNode SHARED =NewNode ();/** Marker to indicate a node was waiting in exclusive mode * / Static FinalNode EXCLUSIVE =NULL;PrivateNodeAddwaiter(Node mode) {Node node =NewNode (Thread.CurrentThread(), mode);//Try a quick queue operation because most of the time the tail node is not NULLNode pred = tail;if(Pred! =NULL) {node.prev= pred;if(Compareandsettail(pred, node)) {pred.Next= node;returnNode }        }//If the tail node is empty (that is, the queue is empty) or if you try to fail CAS (due to concurrency), enter the Enq method        Enq(node);returnNode }

Above is the method of adding a waiter to the waiting queue. First constructs a Node entity, the parameter is the current thread and a mode, this mode has two forms, one is SHARED, one is EXCLUSIVE, see the above code. Then perform the following enqueue operation Addwaiter, and the Else branch operation of the Enq () method is the same, the operation here if successful, you do not have to go into the Enq () method of the loop, you can improve performance. If it is not successful, then call the Enq () method.

private  Node enq  ( Final  node node) {//dead loop +cas Ensure all nodes are queued  for  (;;) {Node t = tail; //if the queue is empty set an empty node as the head  if  (t = = null ) {//must initialize  if  (compareandsethead  (new  Node ())) tail = head; } else  {//join Team tail  node. = t; if  (compareandsettail  (t, node)) {T.next  = node; return  t; } } } }

Description: Cyclic plus CAS operation is the standard way to implement optimistic locking, CAS is to achieve atomic operations, the so-called atomic operation is not affected by other threads during the execution of the operation. The Java implementation of the CAS is called the Unsafe class provides methods, the bottom is to call the C + + method, directly manipulate the memory, on the CPU level lock, directly to the memory operation.

Above is the AQS waiting team to be included in the team method, the operation in the infinite loop, if the queue successfully returned to the new team tail node, otherwise always spin until the queue success. Assume that the queue node is nodes, come up directly into the loop, in the loop, first get the tail node.

1, if branch, if the tail node is NULL, indicating that there are no waiting threads in the queue, try the CAS operation to initialize the head node, and then set the tail node as the head node, because the initialization is the same as the end of the same time, which is related to AQS design implementation, AQS default to have a virtual node. At this point, the tail node is not empty, the loop continues, into the Else branch;

2, Else branch, if the tail node is not null, Node.prev = T, that is, the current tail node is set to the node to be queued to the predecessor node. Then again using the CAS operation, the node to be queued is set as the tail node of the queue, if the CAS returns false, indicating that no success is set, continue the loop setting until the setting succeeds, then set the next property of the previous tail node (that is, the penultimate node) to the current tail node, corresponding to T.next = Node statement, and then returns the current tail node, exiting the loop.

The Setheadandpropagate method is responsible for waking the spinning wait or thread that is blocked by the locksupport.

Private void setheadandpropagate(Node node,intPropagate) {//Back up the current headNode h = head;//The thread that grabbed the lock was awakened to set the node to head        Sethead(node)//propagate generally greater than 0 or there are threads that can be awakened        if(Propagate >0|| H = =NULL|| H.Waitstatus<0|| (h = head) = =NULL|| H.Waitstatus<0) {node S = node.Next;///Only one node or shared mode frees all waiting threads to try preemption locks individually            if(s = =NULL|| S.isShared())doreleaseshared(); }    }

One of the attributes in the Node object is Waitstatus, which has four states, namely:

//线程已被 cancelled ,这种状态的节点将会被忽略,并移出队列staticfinalint CANCELLED =  1;// 表示当前线程已被挂起,并且后继节点可以尝试抢占锁staticfinalint SIGNAL    = -1;//线程正在等待某些条件staticfinalint CONDITION = -2;//共享模式下 无条件所有等待线程尝试抢占锁staticfinalint PROPAGATE = -3;
waiting for thread to be awakened

When executing Countdownlatch's countdown () method, the counter is reduced by one, that is, state minus one, and when it is reduced to 0, the threads in the wait queue are freed. is called AQS's Releaseshared method to implement, the following code in the method is called sequentially, picked up together, easy to see:

//Aqs class Public Final Boolean releaseshared(intARG) {//ARG is a fixed value of 1        //If the counter state is 0 returns true if the call to Countdown () cannot have been 0        if(tryreleaseshared(ARG)) {//Wake up waiting queue thread            doreleaseshared();return true; }return false; }//Countdownlatch overridden methodprotected Boolean tryreleaseshared(intReleases) {//decrement count; signal when transition to zero            //Is still a cyclic +cas with a counter minus 1             for(;;) {intc =getState();if(c = =0)return false;intNEXTC = C-1;if(compareandsetstate(c, NEXTC))returnNEXTC = =0; }        }/// Aqs class Private void doreleaseshared() { for(;;) {Node h = head;if(H! =NULL&& h! = tail) {intWS = H.Waitstatus;//If the node status is signal, then his next node can also try to wake up                if(ws = = Node.)SIGNAL) {if(!Compareandsetwaitstatus(h, Node.)SIGNAL,0))Continue;//Loop to recheck cases                    Unparksuccessor(h); }//Set the node state to propagate, indicating that you want to propagate down, wake up                Else if(WS = =0&&!Compareandsetwaitstatus(H,0, Node.PROPAGATE))Continue;//Loop on failed CAS}if(h = = head)//Loop if head changed                 Break; }    }

Because this is a shared type, when the counter is 0, all threads in the wait queue are awakened, and all threads that call the await () method are woken up and executed concurrently. This scenario corresponds to the scenario where there are multiple threads that need to wait for some action to complete, such as one thread completing the initialization action and the other 5 threads having to use the initialized result, and the other 5 threads are waiting before the initialization thread calls countdown. Once the initialization thread invokes countdown, the other 5 threads are woken up and executed.

Summary

1, AQS is divided into exclusive mode and sharing mode, Countdownlatch uses its sharing mode.

2, AQS when the first waiting thread (wrapped as Node) to queue, to ensure that there is a head node, the head node is not associated with the thread, that is, a virtual node.

3. When the waiting node in the queue (associated thread, non-head node) grabs the lock, the node is set to the head node.

4, the first spin grab lock failed, Waitstatus will be set to 1 (SIGNAL), the second failure, will be locksupport blocking hangs.

5, if the predecessor node of a node is SIGNAL state, then this node can try to preempt the lock.

You might as well go to my public number and interact: ancient kites

Java multi-thread---using countdownlatch to illustrate the principle of AQS implementation

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.