目录
- 一、CountDownLatch简介
- 二、源码
- 三、使用
- 3.1 demo1
- 3.2 demo2
- 四、应用场景
- 五、参考链接
一、CountDownLatch简介
CountDownLatch
(倒计时锁存器)是Java并发包中的一个工具类,用于实现多个线程之间的同步。它通过一个计数器来实现线程之间的等待和唤醒操作,允许一个或多个线程等待其他线程完成操作后再继续执行。
CountDownLatch的主要特点包括:
初始值设定
:CountDownLatch被创建时需要指定一个初始计数值,表示需要等待的线程数量。
计数操作
:通过countDown()方法对计数器进行减1操作,表示一个线程已经完成了任务。
等待操作
:通过await()方法使当前线程等待,直到计数器值减至0。
唤醒操作
:当计数器值减至0时,所有等待的线程将被唤醒继续执行。
CountDownLatch通常用于一组线程等待另一组线程完成任务后再继续执行,或者实现多个线程协同工作的场景。它可以帮助解决线程之间的依赖关系和协同问题,提高程序的并发性能和效率。
二、源码
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
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;
}
}
}
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
逐一解读各个方法
内部
Sync
类:
- Sync类的构造函数:接受一个初始计数值count,并将其设置为当前状态。
- getCount()方法:返回当前计数值。
- tryAcquireShared(int acquires)方法:
当尝试获取共享锁时调用。
如果当前状态为0,表示可以获取锁,返回1;否则返回-1。- tryReleaseShared(int releases)方法:
当释放共享锁时调用。
通过循环不断尝试将状态值减1,直到减至0,然后返回false;循环过程中尝试使用CAS(Compare and Set)操作来将当前状态值更新为下一个状态值,并在更新成功后判断下一个状态值是否为0。简而言之,当前状态值为0返回false;CAS设置成功,下一个状态值为0返回true,否则返回false。
CountDownLatch
类:- 构造函数CountDownLatch(int count):创建一个CountDownLatch对象,接受一个初始计数值,如果初始计数值小于0,则抛出IllegalArgumentException异常。
- await()方法:阻塞等待直到计数值为0,如果被中断则抛出InterruptedException异常。
- await(long timeout, TimeUnit unit)方法:在指定的时间范围内等待,如果在超时之前计数值变为0则返回true,否则返回false。
- countDown()方法:将计数值减1。
- getCount()方法:返回当前计数值。
- toString()方法:返回当前对象的字符串表示,包括计数值信息。
三、使用
3.1 demo1
public static void test1() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
//所有请求都阻塞在这,等待
countDownLatch.await();
// 调用测试接口
System.out.println(Thread.currentThread().getName() + "开始执行……");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
System.out.println(Thread.currentThread().getName() + "主线程开始睡眠……");
// 让请求都准备好
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "主线程睡眠完成……");
// 让所有请求统一请求
countDownLatch.countDown();
}
通过CountDownLatch.await(),让多个参与者线程启动后阻塞等待,然后在主线程 调用CountDownLatch.countdown() 将计数减为0,让所有线程一起往下执行;以此实现了多个线程在同一时刻并发执行,来模拟并发请求的目的。
输出:
3.2 demo2
public static void test1() {
final CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
System.out.println("Task is running..."+ Thread.currentThread().getName());
latch.countDown();
System.out.println("Task is completed."+ Thread.currentThread().getName());
};
// 启动3个线程执行任务
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
try {
latch.await(); // 等待所有任务完成
System.out.println("All tasks are completed. Proceed with main task.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这一次主线程wait阻塞,等到子线程() latch.countDown()运行完才继续执行主线程。
输出:
通过上面两个例子可以看到我们主要使用了await阻塞方法和countDown()减少计数值来控制多线程的运行。
四、应用场景
场景1 让多个线程等待:模拟并发,让并发线程一起执行,见demo1
场景2 让单个线程等待:多个线程(任务)完成后,进行汇总合并,见demo2
鄙人尝试单独使用CountDownLatch控制多个子线程的并发顺序,效果并不好,会添加很多变量,消耗额外资源,不建议在该场景下使用。
五、参考链接
Java高并发编程基础三大利器之CountDownLatch
CountDownLatch的两种常用场景
按照惯例,附美图一张