CountDownLatch
concept
-
The countDownLatch class enables a thread to wait for other threads to execute after their own execution.
-
It is implemented through a counter. The initial value of the counter is the number of threads. Every time a thread is executed, the counter value is - 1. When the counter value is 0, it means that all threads are executed, and then the threads waiting on the lock can resume work.
Simple use
The following code blocks multiple threads. Other waiting threads will be executed only when the counter is 0. Open three threads to make them in the waiting state. Only the main thread will count - 1, and the waiting thread can execute.
public class CountDownExample { //Definition, set counter in construction method static CountDownLatch countDownLatch = new CountDownLatch(1); static class Thread1 extends Thread { @Override public void run() { try { countDownLatch.await(); System.out.println("thread1"); } catch (InterruptedException e) { e.printStackTrace(); } //It means I'm done } } static class Thread2 extends Thread { @Override public void run() { try { countDownLatch.await(); System.out.println("thread2"); } catch (InterruptedException e) { e.printStackTrace(); } } } static class Thread3 extends Thread { @Override public void run() { try { countDownLatch.await(); System.out.println("thread3"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { Thread1 t1 = new Thread1(); t1.start(); Thread2 t2 = new Thread2(); t2.start(); Thread3 t3 = new Thread3(); t3.start(); System.out.println("Start execution"); Thread.sleep(1000); //Counter - 1 countDownLatch.countDown(); } }
Demonstration effect:
Start execution thread1 thread2 thread3
Source code
-
Only one constructor is provided in the countDownLatch class:
//The parameter count is a count value public CountDownLatch(int count) { };
-
There are three methods in the class that are most important:
//The thread calling the await() method is suspended and waits until the count value is 0 public void await() throws InterruptedException { }; //Similar to await(), it will continue to execute if the count value has not changed to 0 after waiting for a certain time public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //Subtract the count value by 1 public void countDown() { };
practical application
When starting the application, check the health of the third-party application. Other operations can only be performed if all are qualified.
- Create an abstract class
public abstract class BaseHealthChecker implements Runnable { private String serviceName; //Service name private boolean serviceUp; public BaseHealthChecker(String serviceName) { this.serviceName = serviceName; } @Override public void run() { try { verifyService(); serviceUp = true; } catch (Exception e) { serviceUp = false; e.printStackTrace(); } } /** * Check the health of the service */ public abstract void verifyService() throws Exception; public String getServiceName() { return serviceName; } public boolean isServiceUp() { return serviceUp; } }
- Create two services
public class CacheHealthChecker extends BaseHealthChecker { private CountDownLatch countDownLatch; public CacheHealthChecker(CountDownLatch countDownLatch) { super("CacheHealthChecker"); this.countDownLatch = countDownLatch; } @Override public void verifyService() throws Exception { System.out.println("Checking:" + this.getServiceName()); try { Thread.sleep(1000); // throw RuntimeException() if the check fails } catch (Exception e) { throw e; } //Counter - 1 countDownLatch.countDown(); System.out.println(this.getServiceName() + " Normal health"); } }
public class DatabaseHealthChecker extends BaseHealthChecker { private CountDownLatch countDownLatch; public DatabaseHealthChecker(CountDownLatch countDownLatch) { super("DatabaseHealthChecker"); this.countDownLatch = countDownLatch; } @Override public void verifyService() throws Exception { System.out.println("Checking:" + this.getServiceName()); try { Thread.sleep(1000); } catch (Exception e) { throw e; } //Counter - 1 countDownLatch.countDown(); System.out.println(this.getServiceName() + " Normal health"); } }
- Start the class and check the service first
public class ApplicationStartup { private static List<BaseHealthChecker> services; private static CountDownLatch countDownLatch = new CountDownLatch(2); static { services = new ArrayList<>(); services.add(new CacheHealthChecker(countDownLatch)); services.add(new DatabaseHealthChecker(countDownLatch)); } //Singleton mode private final static ApplicationStartup INSTANCE = new ApplicationStartup(); private ApplicationStartup() { } public static ApplicationStartup getInstance() { return INSTANCE; } public static boolean checkExternalServices() throws InterruptedException { for (BaseHealthChecker bh : services) { new Thread(bh).start(); //Threads are used to execute for each service } //Wait for the check to complete countDownLatch.await(); return true; } }
- Test class
public class StartupMain { public static void main(String[] args) { try { ApplicationStartup.checkExternalServices(); } catch (InterruptedException e) { //There's a problem. Do something else e.printStackTrace(); } System.out.println("Service started successfully"); } }
- Demonstration effect
Checking:CacheHealthChecker Checking:DatabaseHealthChecker CacheHealthChecker Normal health DatabaseHealthChecker Normal health Service started successfully
Differences between CountDownLatch and CyclicBarrier:
1.countDownLatch is a counter. When a thread completes a record, the counter decreases. It can only be used once
2. The cyclicbarrier counter is more like a valve. It needs all threads to arrive and then continue to execute. The counter is incremented and provides the reset function, which can be used multiple times
Semaphore
concept
Semaphore is usually called semaphore, which can be used to control the number of threads accessing specific resources at the same time, and ensure rational use of resources by coordinating various threads.
It can be simply understood as the display screen standing at the entrance of our parking lot. Every time a car enters the parking lot, the display screen will show the remaining parking space minus 1. Every time a car goes out of the parking lot, the remaining vehicles displayed on the display screen will increase 1. When the remaining parking space on the display screen is 0, the railing at the entrance of the parking lot will not be opened again, and the vehicles will not be able to enter the parking lot, Until a car goes out of the parking lot.
Usage scenario
It is usually used in scenarios where there are clear access restrictions on resources, and it is often used for flow restriction.
For example, in the database connection pool, there is a limit on the number of threads connecting at the same time, and the number of connections cannot exceed a certain number. When the number of connections reaches the limit, the subsequent threads can only queue up and wait until the previous thread releases the database connection to obtain the database connection.
For example, in the parking lot scene, the number of parking spaces is limited, and only how many cars can be accommodated at the same time. When the parking spaces are full, only cars outside the parking lot can enter.
Method description
acquire() Get a token. The thread is blocked until the token is obtained or interrupted by other threads. acquire(int permits) Get a token. The thread is blocked until the token is obtained, interrupted or timed out by other threads. acquireUninterruptibly() Get a token. The thread is blocked (ignoring interrupt) until the token is obtained. tryAcquire() When trying to obtain a token, it returns the success or failure of obtaining the token without blocking the thread. tryAcquire(long timeout, TimeUnit unit) Try to obtain the token, and try to obtain it in a cycle within the timeout period until the attempt is successful or the timeout returns, without blocking the thread. release() Release a token and wake up a blocked thread that failed to obtain the token. hasQueuedThreads() Whether there are still waiting threads in the waiting queue. getQueueLength() Gets the number of blocked threads in the wait queue. drainPermits() Empty token sets the number of available tokens to 0 and returns the number of empty tokens. availablePermits() Returns the number of tokens available.
Define a Semaphore with 10 tokens.
Semaphore semaphore=new Semaphore(10);
acquire = 10 - 1
- When it is 0, it is blocked
- It is possible to block N threads at the same time
release = token + 1
- There's a token. Wake up
- Wake up from blocked threads.
Why use shared locks?
Because multiple tokens can be released at the same time, it means that multiple threads can preempt the lock at the same time.
Preliminary use
We simulate a parking lot using semaphores.
When there is no parking space, other vehicles cannot enter; Must wait for a new vacancy.
public class SemaphoreExample { public static void main(String[] args) { //Limit the concurrent number of resources. It can be understood that there are only 10 parking spaces. Semaphore semaphore = new Semaphore(10); for (int i = 0; i < 20; i++) { new Car(i, semaphore).start(); } } static class Car extends Thread { private int num; private Semaphore semaphore; public Car(int num, Semaphore semaphore) { this.num = num; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); //Get a token System.out.println("The first " + num + "The two cars grabbed a parking space"); TimeUnit.SECONDS.sleep(2); System.out.println("The first " + num + "Let's go~"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); //Release a token } } } }
Implementation principle
initialization
Semaphore semaphore = new Semaphore(10);
1. When the new Semaphore(10) method is called, a synchronous blocking queue of unfair locks will be created by default.
2. Assign the initial number of tokens to the state state of the synchronization queue, and the value of state represents the number of tokens left at present.
public Semaphore(int permits) { sync = new NonfairSync(permits); } Sync(int permits) { setState(permits); }
Get token
semaphore.acquire();
1. The current thread will try to synchronize the queue to obtain a token. The process of obtaining a token is to modify the state of the synchronization queue using atomic operations. If a token is obtained, it will be modified to state=state-1.
2. When the calculated state < 0, it indicates that the number of tokens is insufficient. At this time, a Node will be created to join the blocking queue and suspend the current thread.
3. When the calculated state > = 0, it means that the token is obtained successfully.
/** * Get 1 token */ public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } /** * Obtain the token in the shared mode. If the token is obtained successfully, it will be returned. If it fails, it will join the blocking queue and suspend the thread * @param arg * @throws InterruptedException */ public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //Try to obtain tokens. arg is the number of tokens obtained. When the number of available tokens minus the current number of tokens is less than 0, create a node to join the blocking queue and suspend the current thread. if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } /** * 1,Create a node and join the blocking queue, * 2,Reset the head and tail node relationship of the two-way linked list, and clear the invalid nodes * 3,Suspend current node thread * @param arg * @throws InterruptedException */ private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { //Create a node to join the blocking queue final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { //Get current node pre node final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg);//Returns the state of the lock if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } //Reorganize the two-way linked list, empty the invalid nodes, and suspend the current thread if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
release token
semaphore.release();
1. The thread will try to release a token. The process of releasing the token is to change the state of the synchronization queue to state=state+1
2. After releasing the token successfully, a thread in the synchronization queue will be awakened at the same time.
3. The awakened node will retry to modify the operation of state=state-1. If state > = 0, the token will be obtained successfully. Otherwise, it will re-enter the blocking queue and suspend the thread.
/** * release token */ public void release() { sync.releaseShared(1); } /** *Releasing the shared lock wakes up a thread in the synchronization queue. * @param arg * @return */ public final boolean releaseShared(int arg) { //Release shared lock if (tryReleaseShared(arg)) { //Wake up all shared node threads doReleaseShared(); return true; } return false; } /** * Wake up a thread in the synchronization queue */ private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) {//Whether to wake up the successor node if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//The modification status is initial 0 continue; unparkSuccessor(h);//Wake up h.nex node thread } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)); } if (h == head) // loop if head changed break; } }
CyclicBarrier
concept
From the literal meaning, we can know that the Chinese meaning of this class is "circular fence". It roughly means a recyclable barrier.
Its function is to make all threads wait for completion before proceeding to the next step. This is equivalent to multiple threads blocking through the await of CountDownLatch. Then another thread wakes up using the countDown method.
For example, in life, we will invite friends to a restaurant for dinner. Some friends may arrive early and some friends may arrive late, but the restaurant stipulates that we will not be allowed in until everyone arrives. The friends here are all threads, and the restaurant is CyclicBarrier.
Usage scenario
It can be used in the scenario of multi-threaded calculation of data and finally merging the calculation results.
Method description
public CyclicBarrier(int parties) public CyclicBarrier(int parties, Runnable barrierAction)
- parties is the number of participating threads
- The second constructor has a Runnable parameter, which means the task to be done by the last arriving thread
public int await() throws InterruptedException, BrokenBarrierException public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
- The thread calls await() to indicate that it has reached the fence
- BrokenBarrierException indicates that the fence has been damaged. The reason for the damage may be that one of the threads was interrupted or timed out while await()
Simple use
public class CyclicBarrierExample { public static void main(String[] args) { //After all four tasks arrive at the fence, carry out the next step int n = 4; CyclicBarrier barrier = new CyclicBarrier(4, () -> { System.out.println("All threads have finished writing and continue to process other tasks"); }); // 4 for (int i = 0; i < n; i++) { new Writer(barrier).start(); } } static class Writer extends Thread { private CyclicBarrier cyclicBarrier; public Writer(CyclicBarrier barrier) { this.cyclicBarrier = barrier; } @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "After writing data, wait for other threads"); cyclicBarrier.await(); //-Action of 1; It's the fence } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } }
Operation effect:
Thread-3 After writing data, wait for other threads Thread-1 After writing data, wait for other threads Thread-0 After writing data, wait for other threads Thread-2 After writing data, wait for other threads All threads have finished writing and continue to process other tasks