Take a look at the locks CountDownLatch and CyclicBarrier in Java
- Preface
- Basic introduction
- Use and difference
- Core source code analysis
- summary
Preface
Several articles have been written in Java JUC package. First of all, I spent 5 articles on AQS from the perspective of source code analysis. Why do I spend 5 articles on AQS? It's because AQS is really important. Many implementations provided in JUC are based on AQS, so it's very important to understand the code in AQS Read more times and search for articles online. There are many ways and materials to learn in this time. As long as you are willing to spend time, I believe you will know more than others!
OK, there are a lot of nonsense today. Let's get to the main point. I also continued to write the third article of java lock CountDownLatch, but I found that CyclicBarrier is similar to it in function, but in fact, they are different implementations, and many people on the Internet doubt the usage scenarios and differences of the two. Let's write it together by the way today
Basic introduction
CountDownLatch
CountDownLatch means locking and countdown locking. Both CountDownLatch and semaphore are the realization of thread sharing mode in AQS, which allows multiple threads to have one resource at the same time. Semaphore is to control the concurrent number of threads. In other words, it is to control the number of threads occupying resources. CountDownLatch is to create locked threads Wait for multiple threads that occupy resources to execute before executing their own methods. The explanation may be a little obscure. I will describe it in demo below
CyclicBarrier
CyclicBarrier is a barrier lock compiled in Chinese, and it can be recycled. I'll explain it in the source code later. It means that multiple threads can wait under one condition, which is equivalent to a barrier. Only when all threads arrive, can you continue to do other things. If you don't understand it for the moment, keep looking down. I'm sure you can understand it later!
Use and difference
Use of CountDownLatch
After all, let's use a Demo to see the specific usage:
/** * @ClassName CountDownLatchDemo * @Auther burgxun * @Description: Countdown lock demo * @Date 2020/4/11 12:51 **/ public class CountDownLatchDemo { public static void main(String[] args) { long timeNow = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(3); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 3, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5)); poolExecutor.prestartCoreThread(); try { PrintLog("Perform health check of seckill system"); poolExecutor.execute(new CheckMQ(countDownLatch)); poolExecutor.execute(new CheckRPCInterface(countDownLatch)); poolExecutor.execute(new PreRedisData(countDownLatch)); countDownLatch.await(); PrintLog("After the health examination is completed, the total cost is:" + (System.currentTimeMillis() - timeNow)); } catch (InterruptedException e) { e.printStackTrace(); } finally { poolExecutor.shutdown(); } } public static void PrintLog(String logContent) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm.ss.SSS"); System.out.println(String.format("%s : %s", simpleDateFormat.format(new Date()), logContent)); } static class CheckRPCInterface implements Runnable { private CountDownLatch countDownLatch; public CheckRPCInterface(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { PrintLog("RPC Interface detection starts"); Thread.sleep(1000); PrintLog("RPC Interface test completed"); } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } } } static class PreRedisData implements Runnable { private CountDownLatch countDownLatch; public PreRedisData(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { PrintLog("Redis Data start preheating start execution"); Thread.sleep(3000); PrintLog("Redis Data start preheating completed"); } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } } } static class CheckMQ implements Runnable { private CountDownLatch countDownLatch; public CheckMQ(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { PrintLog("MQ Test start"); Thread.sleep(2000); PrintLog("MQ Detection completed"); } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } } } }
Execution result:
org.example.CountDownLatchDemo 2020-04-11 01:23.33.859 : Perform health check of seckill system 2020-04-11 01:23.33.868 : MQ Test start 2020-04-11 01:23.33.870 : RPC Interface detection starts 2020-04-11 01:23.33.870 : Redis Data start preheating start execution 2020-04-11 01:23.34.870 : RPC Interface test completed 2020-04-11 01:23.35.870 : MQ Detection completed 2020-04-11 01:23.36.871 : Redis Data start preheating completed 2020-04-11 01:23.36.871 : Health examination completed, total cost: 3065
What's the above scenario? Let me describe it. I believe that many small partners know more or less about seckill system. For example, we need to go online at 12 o'clock for a seckill system. Before going online, we must make preparations for some work, such as checking some core interfaces, preheating Redis core data, checking MQ message queues, etc. if all these work is completed, We might just open our second kill portal. The above Demo describes such a thing. Of course, some monitoring needs to be done in the production environment. From the above Demo, we can see that when the current thread creating CountDownLatch is executed to countDownLatch.await, the thread is blocked. Only when the remaining detection threads are executed, the current thread will be awakened to execute the subsequent logic,
Note that the blocking thread here is the thread that creates countDownLatch. The rest of the threads will perform the countDownLatch.countDown operation only after the execution is completed! Understanding this is important to understand the difference between understanding and CyclicBarrier
The use of CyclicBarrier
Now I'm looking at the Demo of CyclicBarrier
/** * @ClassName CyclicBarrierDemo * @Auther burgxun * @Description: Barrier lock. The whole family went out to play, Demo * @Date 2020/4/11 14:08 **/ public class CyclicBarrierDemo { private static int NUMBER = 3; public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> { PrintLog("All right, the family can go out for a tour"); }); PrintLog("The whole family is going out for a spring outing"); createThread(cyclicBarrier, "Dad", 2000).start(); createThread(cyclicBarrier, "Mom", 5000).start(); createThread(cyclicBarrier, "Son", 3000).start(); System.out.println("gogogogo!"); } public static Thread createThread(CyclicBarrier cyclicBarrier, String name, int runTime) { Thread thread = new Thread(() -> { try { PrintLog(String.format("%s Get ready to go out", name)); Thread.sleep(runTime); PrintLog(String.format("%s Pick up the flowers.%s Millisecond", name, runTime)); if (cyclicBarrier.getNumberWaiting() < (NUMBER - 1)) { PrintLog(String.format("%s Start waiting....", name)); } long time = System.currentTimeMillis(); cyclicBarrier.await(); PrintLog(String.format("%s I started to put on my shoes. I waited%s Seconds.", name, (System.currentTimeMillis() - time))); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); return thread; } public static void PrintLog(String logContent) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm.ss.SSS"); System.out.println(String.format("%s : %s", simpleDateFormat.format(new Date()), logContent)); } }
Execution result:
2020-04-11 02:33.35.306 : The whole family is going out for a spring outing gogogogo! 2020-04-11 02:33.35.313 : Mom starts to pack up and get ready to go out 2020-04-11 02:33.35.313 : My son began to pack up and get ready to go out 2020-04-11 02:33.35.313 : Dad started to pack up and get ready to go out 2020-04-11 02:33.37.314 : It took dad 2000 milliseconds to clean up 2020-04-11 02:33.37.314 : Dad began to wait.... 2020-04-11 02:33.38.314 : It took my son 3000 milliseconds to clean up 2020-04-11 02:33.38.314 : The son began to wait.... 2020-04-11 02:33.40.313 : Mom took 5000 milliseconds to clean up 2020-04-11 02:33.40.313 : All right, the family can go out for a tour 2020-04-11 02:33.40.314 : Dad started putting on his shoes and I waited 2999 seconds 2020-04-11 02:33.40.314 : Mom started to put on shoes and set off. I've been waiting for 0 seconds 2020-04-11 02:33.40.314 : My son started to put on his shoes. I've been waiting for 2000 seconds
What's the demo above? It's a demo for the whole family to go out, Since you want to go out to play, you must be prepared. Mom needs to make up and change clothes. My son may choose a toy he loves. Dad doesn't think it's OK. Just change clothes. The rest will wait for my son and mom. Only when the whole family is ready, can everyone wear shoes. Then walk to the parking lot and drive away!
It should be noted that the thread creating the CyclicBarrier is non blocking. From the above log of executing gogogo, it can be seen that the blocked thread is the thread executing cyclicBarrier.await, which is the task thread. This is to wait. As long as all threads have reached a point of execution, they can continue to execute the code behind await
If you still don't understand the Demo above, I can say that you must know that for example, a product must be sold to three people in order to enjoy the discount successfully. When the first person buys, he / she orders and waits, but can't pay in a group. The second person is also, until the third person arrives, he / she will be notified to the first person and the second person You can enjoy the preferential price after the order is completed. Now you can pay! Of course, when the fourth person comes, you still have to wait.. This is the best understanding of CyclicBarrier!!!
Difference between the two
From my bold text above, we can see the difference between the two. The core is that the blocked threads are different,
Countdownlatch is the thread that creates the countdownlatch object to call wait and it will block the current thread. Other threads that perform tasks will not block. Only after the execution is completed, modify the counter value in countdownlatch. When the last thread is completed, the counter value changes to 0, and the blocked creation thread will wake up
Cyclicbarrier is the thread that creates the cyclicbarrier object and will not block or call await, Only the thread executing the task will invoke the await method of CyclicBarrier and block the current thread after calling. When the count attribute value in CyclicBarrier is 0, then it is stated that some threads are ready to go through this barrier, wake up the thread calling await before continuing the remaining logic.
I'm going to give you an example of life. I'm sure you've participated in the group building and running of the company. Generally, people are divided into multiple groups. Then each group has a team leader. Take the group building of our company for example. Every year, we go to practice and say that running is running. Running is more than ten kilometers. For the fun of the activity, the company usually sets multiple punch cards in the middle Generally, the team leader will wait for all the team members to arrive at the checkpoint to take a picture and do a task. The team leader's roll call is equivalent to a CountDownLatch. Why do you say that? Because the team leader's roll call is a series of tasks. At this time, he arrives at the checkpoint We have to wait for all the team members to arrive. At this time, the rest of the team members are equivalent to carrying out a cyclical barrier task. Because when the team members arrive at the punch point, they can't continue to walk down. They have to wait for the rest of the team to arrive to continue the next task!
After that, do you understand that CountDownLatch is the team leader's own block to wait for everyone to go together, and cyclicBarrier is the team member who has to wait for other team members to arrive at the punch point to continue the next task!
To be more straightforward, the captain is that the driver has to wait for all the people to start. The team members are the same as the passengers. When they arrive in the bus, they have to wait for all the people to arrive before leaving
Core code analysis
CountDownLatch
First look at the class structure of CountDownLatch. The structure of CountDownLatch is very simple. There is an internal class. Sync continues the AQS class, and the rest is an initialization method and two await methods
Sync
private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; /** * Set the State value in AQS synchronizer */ Sync(int count) { setState(count); } /** * Get the value of State in AQS */ int getCount() { return getState(); } /** * Overriding the AQS method, trying to get the resource if the current status value is equal to 0, then return 1 otherwise return - 1 * Why is this implemented here? Because this lock is a countdown lock. If the current State is not equal to 0 and the return value is - 1, the current thread needs to be blocked * The countDown method is called only when the State is equal to 0, which means that all threads taking up resources have finished execution * At that time, the thread does not need to block the executable, and it will be */ protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } /** * Release resources in shared mode */ protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (; ; ) {//spin int c = getState(); if (c == 0) return false; int nextc = c - 1;//Synchronizer state value State -1 every time it is released if (compareAndSetState(c, nextc))//Why are CAS operations practical? Here and Semaphore semaphores are due to the problem of multithreading return nextc == 0; } } }
I have annotated all the above codes. I believe I can understand them. The method is very simple, mainly two methods that rewrite AQS's tryAcquireShared and tryreleased shared
Internal method
/** * default constructor */ public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } /** * The method of the resource acquired by AQS in shared mode responds to the version of the interrupt */ public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } /** * Access to resources with deadline in sharing mode */ public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } /** * Release synchronizer State resource minus 1 */ public void countDown() { sync.releaseShared(1); } /** * Returns the current synchronizer status value */ public long getCount() { return sync.getCount(); }
The above is CountDownLatch's method, which is not easy to talk about. It calls the method in AQS to realize. This has been explained a lot. If you don't talk about it, don't talk about it. I have a clear idea of the above~
CyclicBarrier
The class structure of CyclicBarrier is totally different from CountDownLatch. Instead of directly inheriting the AQS class, it uses the combination of ReentrantLock and Condition to realize the function.
Constructor
First look at the two constructors:
public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties;//Indicates the number of threads needed to open the Barrier this.count = parties;// Currently, there are still threads that need to open the barrier this.barrierCommand = barrierAction;//Callback function after opening the barrier } /** * Creating a cyclobarrier parties indicates the number of threads needed to open the Barrier */ public CyclicBarrier(int parties) { this(parties, null); }
After reading the constructor, look at the following methods
reset method
/** * break Current barrier and open next barrier */ public void reset() { final ReentrantLock lock = this.lock; lock.lock(); try { breakBarrier(); // Break the current barrier nextGeneration(); // Open a new barrier } finally { lock.unlock(); } } private void breakBarrier() { generation.broken = true;//Set the broken value of the current barrier, count = parties;//Reset the count value the barrier has broken the count value. You need to prepare for the next barrier trip.signalAll();//Wake up all threads previously waiting on this barrier } private void nextGeneration() { trip.signalAll();//All threads waiting on the barrier before waking up count = parties;//Number of barriers to be opened generation = new Generation();//Reset the default value of broken in generation, false }
breakBarrier and nextGeneration do almost the same thing. breakBarrier mainly deals with the current barrier. nextGeneration does something to open a new barrier, which is why it is called CyclicBarrier circular barrier, because it can be opened and closed and then opened again
await method
There are two pulic await methods in the CyclicBarrier, one is waiting without time, the other is waiting with time, and the core is the dowait method of modulation
public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } } public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); } //Core method dowait private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock;//Reentry lock ensures thread safety lock.lock(); try { final Generation g = generation; if (g.broken)//If the current Barrier has broken, a broken Barrier exception will be thrown throw new BrokenBarrierException(); if (Thread.interrupted()) {//If the current thread is interrupted, the breakBarrier is executed and an interrupt exception is thrown breakBarrier(); throw new InterruptedException(); } int index = --count;// Reduce the count value before each execution if (index == 0) { // It indicates that the conditions for opening the Barrier have been reached boolean ranAction = false;//Whether to open the current barrier successfully try { final Runnable command = barrierCommand;//Runnable method to enable the execution of Barrier if (command != null) command.run(); ranAction = true; nextGeneration();//Reset conditions for next Barrier return 0; } finally { if (!ranAction) breakBarrier();//If the open Barrier condition has been reached but the execution is not successful, the Barrier will be destroyed } } /** * loop until * tripped, Wake up by the last thread to Barrier * broken, The current Barrier has been destroyed * interrupted,Blocked waiting thread interrupted including current thread * or timed out Or one of the waiting threads timed out * */ for (; ; ) {//This is a spin try { if (!timed)//Whether there is a waiting time. If there is no waiting time, it will directly block the current thread from entering the tripConditionQueue trip.await(); else if (nanos > 0L)//If there is a waiting time and the waiting time is greater than 0 nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) {//Capture interrupts during thread wait if (g == generation && !g.broken) {// If the generation does not change, it means that it is still in the previous Barrier and the Barrier is not broken breakBarrier();//Then perform the operation of destroying Barrier to wake up the waiting thread throw ie; } else { // We're about to finish waiting even if we had not // been interrupted, so this interrupt is deemed to // "belong" to subsequent execution. Thread.currentThread().interrupt();//Reset interrupt ID } } if (g.broken)//After wake-up, it is found that the Barrier has been broken and a BrokenBarrierException exception is thrown throw new BrokenBarrierException(); /** *After wake-up, it is found that the waiting Barrier is inconsistent with the wake-up Barrier. The Barrier has replaced the previous index * Because a thread can open multiple barriers, such as reset, which will wake up blocked threads. At this time, the current generation object is new * It's obviously not an object */ if (g != generation) return index; if (timed && nanos <= 0L) {//If the waiting time is up, break the barrier and return to TimeoutException breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }
The core of CyclicBarrier's method is here. If you are interested, you can try to understand it by comparing with the source code
summary
CyclicBarrier and CountDownLatch All of them can do yes. Multiple threads wait and then start to do the next step, but the implementation subjects are different. Through the above code and Demo analysis, the implementation subjects of CountDownLatch and the next step operations are the creator's own threads and cannot be reused. The implementation subjects of CyclicBarrier are other threads and can be reused