Introduction
In the previous article, the Reentrantlock principle based on Aqs was analyzed in detail, and Reentrantlock represented an exclusive lock through the transformation between the state variables 0 and 1 in Aqs. So can you think about what it means when the state variable is greater than 1 o'clock? Is there any such implementation based on AQS in J.U.C? If so, how do they all come true? These questions will be answered by analyzing the semaphore and Countdownlatch classes in the j.u.c in detail.
- Shared logic of Semaphore and Countdownlatch
- Examples of use of semaphore and Countdownlatch
Use of 2.1 semaphore
Use of 2.2 Countdownlatch
- SOURCE Analysis
Implementation of shared lock in 3.1 Aqs
3.2 Semaphore Source Analysis
3.3 Countdownlatch Source Analysis
- Summarize
1. How semaphore and Countdownlatch are shared
An exclusive lock means that only one thread can acquire the lock, and the other thread must wait for the lock to be released before the next action can take place. By analogy, does a shared lock mean that the lock can be used by multiple threads at the same time, and does not need to wait? If so, the meaning of the lock will not exist. Sharing in j.u.c means that there are multiple threads that can acquire the lock at the same time, but this number is limited and not infinite, and two kinds of limited shared locks are implemented by semaphore and Countdownlatch respectively in J.U.C.
Semaphore is also called the semaphore, he through a shared ' signal packet ' to each use his thread to assign the signal, when the signal packet is sufficient, the thread can acquire the lock, conversely, the signal packet signal is not enough, you can not obtain the lock, you need to wait for enough signal to be released, to obtain.
Countdownlatch also called the counter, he through a shared count total to control the acquisition of the thread lock, when the total counter is greater than 0 o'clock, the thread will be blocked, not be able to acquire the lock, only if the total counter is 0 o'clock, all blocked threads are released at the same time.
You can see that both semaphore and Countdownlatch have a shared total, which is achieved through state.
2. Examples of use of semaphore and Countdownlatch
Before we analyze the principles of semaphore and countdownlatch in detail, let's take a look at how they are used, so that we can understand their principles later. Know what he is first? And then ask why? The use of semaphore and Countdownlatch is described in detail in two examples below.
Use of 2.1 semaphore
Initialize 10 semaphores in the packet, let ABCD4 threads go to get public static void main (string[] args) throws Interruptedexception {Semaphore Semaphore = new Semaphore (10); Semaphoretest (semaphore);} private static void Semaphoretest (final Semaphore Semaphore) throws interruptedexception {//thread A initially acquires 4 semaphores and then releases the 4 messages 3 times Number thread Threada = new Thread (new Runnable () {@Override public void run () {try { Semaphore.acquire (4); System.out.println (Thread.CurrentThread (). GetName () + "Get 4 semaphore"); Thread.Sleep (2000); System.out.println (Thread.CurrentThread (). GetName () + "Release 1 semaphore"); Semaphore.release (1); Thread.Sleep (2000); System.out.println (Thread.CurrentThread (). GetName () + "Release 1 semaphore"); Semaphore.release (1); Thread.Sleep (2000); System.out.println (Thread.CurrentThread (). GetName () + "Release 2 semaphore"); Semaphore.release (2); } catch (Interruptedexception e) {e.printstacktrace (); } } }); Threada.setname ("Threada"); Thread B initially acquires 5 semaphores and then 2 times releases the 5 semaphores thread threadb = new Thread (new Runnable () {@Override public void run () { try {Semaphore.acquire (5); System.out.println (Thread.CurrentThread (). GetName () + "get 5 semaphore"); Thread.Sleep (2000); System.out.println (Thread.CurrentThread (). GetName () + "Release 2 semaphore"); Semaphore.release (2); Thread.Sleep (2000); System.out.println (Thread.CurrentThread (). GetName () + "Release 3 semaphore"); Semaphore.release (3); } catch (Interruptedexception e) {e.printstacktrace (); } } }); Threadb.setname ("threadb"); Thread C initially acquires 4 semaphores and then 1 times releases the 4 semaphores, thread THREADC = new Thread (new Runnable () { @Override public void Run () {try {Semaphore.acquire (4); System.out.println (Thread.CurrentThread (). GetName () + "Get 4 semaphore"); Thread.Sleep (1000); System.out.println (Thread.CurrentThread (). GetName () + "Release 4 semaphore"); Semaphore.release (4); } catch (Interruptedexception e) {e.printstacktrace (); } } }); Threadc.setname ("THREADC"); Thread D initially acquires 10 semaphores and then releases the 10 semaphores 1 times thread threadd = new Thread (new Runnable () {@Override public void run () {try {semaphore.acquire (10); System.out.println (Thread.CurrentThread (). GetName () + "get ten semaphore"); Thread.Sleep (1000); System.out.println (Thread.CurrentThread (). GetName () + "release ten semaphore"); Semaphore.release (10); } catch (Interruptedexception e) { E.printstacktrace (); } } }); Threadd.setname ("Threadd"); Thread A and thread B first obtained 4 and 5 semaphores respectively, and the total signal was changed to 1 Threada.start (); Threadb.start (); Thread.Sleep (1); Thread C tries to get 4 discoveries that are not enough to wait for Threadc.start (); Thread.Sleep (1); Thread D tries to get 10 discoveries that are not enough to wait for Threadd.start ();}
The results of the implementation are as follows:
threadB get 5 semaphorethreadA get 4 semaphorethreadA release 1 semaphorethreadB release 2 semaphorethreadC get 4 semaphorethreadA release 1 semaphorethreadC release 4 semaphorethreadB release 3 semaphorethreadA release 2 semaphorethreadD get 10 semaphorethreadD release 10 semaphore
You can see that Threada and threadb wait for a semaphore after THREADC and Threadd after acquiring 9 semaphores to continue down execution. Threada and threadb can be executed at the same time when the semaphore is sufficient.
There is a problem, when Threadd queue before THREADC, if the semaphore is released 4, THREADC will be executed before Threadd? Or do you need to wait in line? This question in the detailed analysis of the semaphore source code and then to give everyone the answer.
Use of 2.2 Countdownlatch
Total initialization counter is 2public static void main (string[] args) throws Interruptedexception {Countdownlatch Countdownlatch = new Co Untdownlatch (2); Countdownlatchtest (Countdownlatch);} private static void Countdownlatchtest (final Countdownlatch countdownlatch) throws Interruptedexception {//threada attempted to execute , the counter is 2 blocked Thread Threada = new Thread (new Runnable () {@Override public void run () {try { Countdownlatch.await (); System.out.println (Thread.CurrentThread (). GetName () + "await"); } catch (Interruptedexception e) {e.printstacktrace (); } } }); Threada.setname ("Threada"); THREADB attempts execution, the counter is blocked for 2 thread threadb = new Thread (new Runnable () {@Override public void run () { try {countdownlatch.await (); System.out.println (Thread.CurrentThread (). GetName () + "await"); } catch (Interruptedexception e) { E.printstacktrace (); } } }); Threadb.setname ("threadb"); THREADC minus 1 of the counter after 1 seconds thread THREADC = new Thread (new Runnable () {@Override public void run () { try {thread.sleep (1000); Countdownlatch.countdown (); System.out.println (Thread.CurrentThread (). GetName () + "Countdown"); } catch (Interruptedexception e) {e.printstacktrace (); } } }); Threadc.setname ("THREADC"); Threadd minus 1 of the counter after 5 seconds thread threadd = new Thread (new Runnable () {@Override public void run () { try {thread.sleep (5000); Countdownlatch.countdown (); System.out.println (Thread.CurrentThread (). GetName () + "Countdown"); } catch (Interruptedexception e) {e.printstacktrace (); } } }); Threadd.setname ("thrEadd "); Threada.start (); Threadb.start (); Threadc.start (); Threadd.start ();}
The results of the implementation are as follows:
threadC countDownthreadD countDownthreadA awaitthreadB await
Threada and THREADB when attempting to execute because the total number of counters is 2 blocked, when THREADC and Threadd reduce the total counter to 0, Threada and threadb start executing simultaneously.
To summarize: Semaphore is like a revolving sushi shop with 10 seats, and when the seats are vacant, the waiting person can sit up. If there are only 2 empty seats, and there is a 3-port, then there is only a wait. If it comes to a couple, you can just sit up and eat. Of course, if 5 empty seats are available at the same time, the family of 3 and a couple can go up and eat at the same time. Countdownlatch is like a large shopping mall inside the temporary playground, each play time after waiting for the people at the same game, and a middle will have not love to play the person at any time out, but can not enter, once all entered the people are out, the new batch of people can enter at the same time.
3. Source Code Analysis
Understand what Semaphore and Countdownlatch do, how to use it. The next step is to see how the semaphore and Countdownlatch implement these functions at the bottom.
Implementation of shared lock in 3.1 Aqs
In the last article, through the analysis of Reentrantlock, some key methods to realize exclusive lock in AQS are obtained:
//状态量,独占锁在0和1之间切换private volatile int state;//调用tryAcquire获取锁,获取失败后加入队列中挂起等操作,AQS中实现public final void acquire(int arg);//独占模式下尝试获取锁,ReentrantLock中实现protected boolean tryAcquire(int arg);//调用tryRelease释放锁以及恢复线程等操作,AQS中实现public final boolean release(int arg);//独占模式下尝试释放锁,ReentrantLock中实现protected boolean tryRelease(int arg);
The specific logic for acquiring and releasing exclusive locks is implemented in Reentrantlock, which is responsible for managing the logic that requires specific processing after a successful failure to obtain or release an exclusive lock in Aqs. So does the implementation of shared locks follow this rule? In this way, we found the following similar methods in Aqs:
//调用tryAcquireShared获取锁,获取失败后加入队列中挂起等操作,AQS中实现public final void acquireShared(int arg);//共享模式下尝试获取锁protected int tryAcquireShared(int arg);//调用tryReleaseShared释放锁以及恢复线程等操作,AQS中实现public final boolean releaseShared(int arg);//共享模式下尝试释放锁protected boolean tryReleaseShared(int arg);
Shared lock and Core in the above 4 key methods, first look at how semaphore calls the above method to implement the shared lock.
3.2 Semaphore Source Analysis
The first is the construction method of semaphore, like Reentrantlock, he has two construction methods, so also to achieve fair share lock and non-fair share lock, we may have doubts, since it is a shared lock, why is it fair and not fair? This goes back to the question behind the example above, when the waiting thread is in front of it, whether the subsequent thread can get the semaphore directly or must be queued. Waiting is fair, and jumping in line is not fair.
Or in the case of rotating sushi: Now there are only 2 seats, there is a 3 in the waiting, then a couple, the realization of a fair share of the lock is the couple must wait, only to a 3 of the table after the turn to them, and the realization of the non-fair sharing lock can let the situation directly to eat, because there are just 2 empty Let a family of 3 continue to wait (seems to be very unfair ...) In this case, the advantage of a non-fair sharing lock is that it maximizes the profit of the sushi shop (as well as offending the waiting customer ...). ) is also the default implementation of Semaphore.
public Semaphore(int permits) { sync = new NonfairSync(permits);}public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
The semaphore example uses two core methods, acquire and release, to invoke the acquiresharedinterruptibly and releaseshared methods in Aqs, respectively:
//获取permits个信号量public void acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); sync.acquireSharedInterruptibly(permits);}//释放permits个信号量public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits);}public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) //尝试获取arg个信号量 doAcquireSharedInterruptibly(arg); //获取信号量失败时排队挂起}public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //尝试释放arg个信号量 doReleaseShared(); return true; } return false;}
Semaphore in the process of acquiring and releasing semaphores is accomplished by Aqs, and the actual success or release success is achieved by Semaphore itself.
//公平共享锁尝试获取acquires个信号量protected int tryAcquireShared(int acquires) { for (;;) { if (hasQueuedPredecessors()) //前面是否有排队,有则返回获取失败 return -1; int available = getState(); //剩余的信号量(旋转寿司店剩余的座位) int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) // 剩余信号量不够,够的情况下尝试获取(旋转寿司店座位不够,或者同时来两对情况抢座位) return remaining; }}//非公平共享锁尝试获取acquires个信号量final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); //剩余的信号量(旋转寿司店剩余的座位) int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) // 剩余信号量不够,够的情况下尝试获取(旋转寿司店座位不够,或者同时来两对情侣抢座位) return remaining; }}
You can see the difference between a fair share lock and an unfair shared lock on whether you need to determine whether there are threads waiting in the queue. The fair share lock needs to be judged first, and the non-fair share lock is directly queued, even though the front is already wired for waiting.
To verify this conclusion, slightly modify the example above:
threadA.start();threadB.start();Thread.sleep(1);threadD.start(); //threadD已经在排队Thread.sleep(3500);threadC.start(); //3500毫秒后threadC来插队
Result output:
threadB get 5 semaphorethreadA get 4 semaphorethreadB release 2 semaphorethreadA release 1 semaphorethreadC get 4 semaphore //threadC先与threadD获取到信号量threadA release 1 semaphorethreadB release 3 semaphorethreadC release 4 semaphorethreadA release 2 semaphorethreadD get 10 semaphorethreadD release 10 semaphore
This example is a good illustration of when an unfair lock is attempted to acquire a shared lock before it is queued.
When the acquisition semaphore fails, the queue is queued and the operation is implemented by the Doacquiresharedinterruptibly method in Aqs:
private void doacquiresharedinterruptibly (int arg) throws Interruptedexception {final node node = Addwai ter (node.shared); Join Wait Queue Boolean failed = true; try {for (;;) {final node P = node.predecessor ();//Gets the front node of the current node if (p = = head) {int r = Tryacquir Eshared (ARG); When the predecessor node is the head node, it indicates that the current node is the first suspended thread node and tries again to obtain a shared lock if (r >= 0) {setheadandpropagate (node, r); /Unlike Reentrantlock: Gets the shared lock successfully set the head node, and notifies the next node p.next = null; Help GC failed = false; Return }} if (Shouldparkafterfailedacquire (p, node) &&//non-head node or acquire lock failed, check node status to see if a thread needs to be suspended Parkandcheckinterrupt ())//suspend thread, current thread is blocked here! throw new Interruptedexception (); }} finally {if (failed) cancelacquire (node); }}
This section of code and Reentrantlock in the acquirequeued (Addwaiter (node.exclusive), arg) method is basically the same, say two different places. One is to join the waiting queue when the node.shared type of node is added. Second, when the lock succeeds, the next node is notified, that is, the next thread is awakened. Take the example of rotating sushi shop, in front of the same walk 5 guests, spare 5 seats, a 3-port sitting will tell the back of a couple, let them also sit in, so that they have achieved the purpose of sharing. The Shouldparkafterfailedacquire and Parkandcheckinterrupt methods are described in detail in the previous article, and are explained here.
To see how the Releaseshared method releases the semaphore, first call Tryreleaseshared to try to release the semaphore, and then call doreleaseshared to determine if the subsequent thread needs to be woken up after a successful release:
Protected Final Boolean tryreleaseshared (int releases) {for (;;) {int current = GetState (); int next = current + releases; if (Next < current)//overflow//release semaphore too much throw new Error ("Maximum Permit count exceeded"); if (Compareandsetstate (current, next))//cas operation sets a new semaphore return true; }}private void doreleaseshared () {for (;;) {Node h = head; if (h! = NULL && h! = tail) {int ws = H.waitstatus; if (ws = = node.signal) {//signal-state wake-up successor if (!compareandsetwaitstatus (H, node.signal, 0)) Continue Loop to Recheck cases unparksuccessor (h); Wake succeeding Node} else if (ws = = 0 &&!compareandsetwaitstatus (H, 0, NODE.PR Opagate)) continue; Loop on failed CAS} if (h = = head)//loop if head changed break; }}
The logic of the release is well understood, compared to the reentrantlock just a bit different from the number of state.
3.3 Countdownlatch Source Analysis
Countdownlatch is much simpler to implement than Semaphore, and he does not have a fair and unfair distinction, because when the counter reaches 0, all waiting threads are freed, not 0, and all waiting threads block. Take a direct look at the two core methods of Countdownlatch await and countdown.
public void await () throws Interruptedexception {//and semaphore differ in that the parameter is 1, but this parameter is of little significance to Countdownlatch, Because the tryacquireshared realization behind Countdownlatch is sync.acquiresharedinterruptibly (1) judged by getstate () = =; }public Boolean await (long timeout, timeunit unit) throws Interruptedexception {//Here adds a wait timeout control, which returns false after the time of execution. Code that will not block the return Sync.tryacquiresharednanos for a long time (1, Unit.tonanos (timeout)); }public void Countdown () {sync.releaseshared (1);//1 counts per release}public final void acquiresharedinterruptibly (int arg) Throws Interruptedexception {if (thread.interrupted ()) throw new Interruptedexception (); if (tryacquireshared (ARG) < 0)//try to get arg semaphore doacquiresharedinterruptibly (ARG); The get semaphore fails when queued pending}protected int tryacquireshared (int acquires) {return (getState () = = 0)? 1:1;//lays the foundation for acquiring locks at the same time, regardless of the initial state How many start, only count equals 0 o'clock trigger}
There are two differences between the
and semaphore, one is that state only reduces by 1 at a time, and all waiting threads are freed only 0 o'clock. The second is to provide a timeout waiting method. Acquiresharedinterruptibly method with semaphore like, do not elaborate, here the emphasis is Tryacquiresharednanos method.
Public final Boolean Tryacquiresharednanos (int arg, long nanostimeout) throws Interruptedexception {if (Thread. Interrupted ()) throw new Interruptedexception (); return tryacquireshared (ARG) >= 0 | | Doacquiresharednanos (ARG, nanostimeout);} Minimum spin time static final Long Spinfortimeoutthreshold = 1000l;private boolean doacquiresharednanos (int arg, long Nanostimeout) throws Interruptedexception {if (nanostimeout <= 0L) return false; Final long deadline = System.nanotime () + nanostimeout; Calculates a deadline final node node = addwaiter (node.shared); Boolean failed = true; try {for (;;) {final Node P = node.predecessor (); if (p = = head) {int r = tryacquireshared (ARG); if (r >= 0) {setheadandpropagate (node, r); P.next = null; Help GC failed = false; return true; } } Nanostimeout = Deadline-system.nanotime (); if (nanostimeout <= 0L)//After timeout, return false directly, continue execution return false; if (Shouldparkafterfailedacquire (p, node) && nanostimeout > Spinfortimeoutthreshold)//greater than minimum CAS operation The time hangs thread Locksupport.parknanos (this, nanostimeout); The suspend thread also has a timeout limit if (thread.interrupted ()) throw new Interruptedexception (); }} finally {if (failed) cancelacquire (node); }}
Focus on the comments of a few lines of code, first calculated a time-out, when the timeout after the direct exit wait, continue to execute. If it is not timed out and is greater than the minimum CAS operation time, defined here is 1000ns, then hangs, and the suspend operation also has a timeout limit. This enables a timeout wait.
4. Summary
At this point the two implementations of Aqs shared lock semaphore and Countdownlatch are analyzed, the biggest difference between them and unshared is whether multiple threads can acquire locks at the same time. After reading hope that we can have a deep understanding of semaphore and countdownlatch, do not understand the time to think about rotating sushi shops and amusement Park example, if you have help, feel good to write, can point a praise, Of course, we hope that we can actively point out the mistakes in the text and put forward positive suggestions for improvement.