The use and source code analysis of CountDownLatch
CountDownLatch is commonly known as a lock, which allows one or more threads to wait for other threads to complete the specified operation before running.
The constructor of CountDownLatch accepts an int type parameter as a counter. If you want to wait for the completion of N points, pass in N here.
When we call the countDown method of CountDownLatch, N will be reduced by 1, and the await method of CountDownLatch will block the current thread until N becomes zero.
Since the countDown method can be used anywhere, the N points mentioned here can be N threads, or N execution steps in a thread (a thread can countDown multiple times). When used in multiple threads, just pass the CountDownLatch reference to the thread.
Methods in CountDownLatch
Method name | Description |
---|---|
CountDownLatch(int count) | Construct a CountDownLatch with a given count |
void await() | The current thread waits until the lock count reaches zero, unless the thread is interrupted |
boolean await(long timeout, TimeUnit unit) | The current thread waits until the lock count reaches zero, unless the thread is interrupted or timed out |
void countDown() | Decrease the locked count, if the count reaches zero, wake up all blocked waiting threads |
long getCount() | Return current count |
Use of CountDownLatch
If there is such a requirement: we need to parse the data of multiple sheets in an Excel. At this time, we can consider using multi-threading. Each thread parses the data in a sheet. After all the sheets are parsed, the program needs to prompt that the analysis is complete. .
package com.morris.concurrent.tool.countdownlatch.api;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 演示CountDownLatch闭锁的使用
*/
@Slf4j
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
new Thread(()->parse("sheet1"), "t1").start();
new Thread(()->parse("sheet2"), "t2").start();
new Thread(()->parse("sheet3"), "t3").start();
countDownLatch.await();
log.info("parse commplete");
}
private static void parse(String sheet) {
log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
try {
TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
countDownLatch.countDown();
log.info("还有{}个sheet未解析完", countDownLatch.getCount());
}
}
The results are as follows:
2020-09-24 13:58:55,551 INFO [t2] (CountDownLatchDemo.java:28) - t2 parse sheet2 begin...
2020-09-24 13:58:55,565 INFO [t1] (CountDownLatchDemo.java:28) - t1 parse sheet1 begin...
2020-09-24 13:58:55,597 INFO [t3] (CountDownLatchDemo.java:28) - t3 parse sheet3 begin...
2020-09-24 13:59:01,565 INFO [t1] (CountDownLatchDemo.java:34) - t1 parse sheet1 end.
2020-09-24 13:59:01,566 INFO [t1] (CountDownLatchDemo.java:36) - 还有2个sheet未解析完
2020-09-24 13:59:02,599 INFO [t3] (CountDownLatchDemo.java:34) - t3 parse sheet3 end.
2020-09-24 13:59:02,599 INFO [t3] (CountDownLatchDemo.java:36) - 还有1个sheet未解析完
2020-09-24 13:59:24,556 INFO [t2] (CountDownLatchDemo.java:34) - t2 parse sheet2 end.
2020-09-24 13:59:24,556 INFO [t2] (CountDownLatchDemo.java:36) - 还有0个sheet未解析完
2020-09-24 13:59:24,556 INFO [main] (CountDownLatchDemo.java:24) - parse commplete
Of course, you can also use join() to achieve:
package com.morris.concurrent.tool.countdownlatch.api;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 使用join完成对excel的解析,与countDownLatch对比
*/
@Slf4j
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> parse("sheet1"), "t1");
t1.start();
Thread t2 = new Thread(() -> parse("sheet2"), "t2");
t2.start();
Thread t3 = new Thread(() -> parse("sheet3"), "t3");
t3.start();
t1.join();
t2.join();
t3.join();
log.info("parse commplete");
}
public static void parse(String sheet) {
log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
try {
TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
}
}
The results are as follows:
2020-09-24 14:04:57,732 INFO [t2] (JoinDemo.java:30) - t2 parse sheet2 begin...
2020-09-24 14:04:57,755 INFO [t3] (JoinDemo.java:30) - t3 parse sheet3 begin...
2020-09-24 14:04:57,736 INFO [t1] (JoinDemo.java:30) - t1 parse sheet1 begin...
2020-09-24 14:05:07,757 INFO [t3] (JoinDemo.java:36) - t3 parse sheet3 end.
2020-09-24 14:05:07,758 INFO [t1] (JoinDemo.java:36) - t1 parse sheet1 end.
2020-09-24 14:05:07,757 INFO [t2] (JoinDemo.java:36) - t2 parse sheet2 end.
2020-09-24 14:05:07,759 INFO [main] (JoinDemo.java:26) - parse commplete
Comparison of CountDownLatch and join:
- CountDownLatch is more flexible than join. Join must get the thread object before it can be used. Usually, the thread pool is used. The threads in the thread pool are not exposed to the outside, and join cannot be used.
- Join must wait for the thread to finish before returning, and CountDownLatch.await() can return after the thread is running halfway.
Source code analysis of CountDownLatch
The bottom layer of CountDownLatch is based on AQS.
data structure
java.util.concurrent.CountDownLatch.Sync
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count); // 构造时初始化count的大小
}
int getCount() {
return getState();
}
// await()调用此方法,不为0就会进入同步队列中等待,为0就会直接返回,往下执行
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// countDown()调用此方法,每调用一次state就会-1,当state=0时,会去唤醒同步队列中等待的线程
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
countDown()
java.util.concurrent.CountDownLatch#countDown
public void countDown() {
sync.releaseShared(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// state-1
doReleaseShared(); // 唤醒同步队列中等待的线程
return true;
}
return false;
}
await()
java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 判断state是否为0,是就会直接返回
doAcquireSharedInterruptibly(arg); // 进入同步队列中等待,park
}
Custom CountDownLatch
package com.morris.concurrent.tool.countdownlatch.my;
import java.util.concurrent.TimeUnit;
/**
* 使用wait-notify实现CountDownLatch
*/
public class WaitNotifyCountDownLatch {
private volatile int count;
public WaitNotifyCountDownLatch(int count) {
this.count = count;
}
public synchronized void countDown() {
if (0 == --count) {
this.notifyAll();
}
}
public synchronized void await() throws InterruptedException {
while (count > 0) {
this.wait();
}
}
public synchronized void await(long timeout, TimeUnit unit) throws InterruptedException {
while (count > 0) {
this.wait(unit.toMillis(timeout));
}
}
public int getCount() {
return count;
}
}