CountDownLatch基本使用及原理
- 一、CountDownLatch简介
- 二、CountDownLatch类的继承关系
- 1. AbstractQueuedSynchronizer: 用于构建锁和同步器的框架。
- 2. Sync: CountDownLatch的内部类,提供了锁的具体实现。
- 三、Semaphore的基本使用
- 1. 使用场景:
- 2. 代码实现:
- 3. 运行结果:
- 4. 案例分析:
- 四、CountDownLatch的优缺点
- 1. 优点:
- 1.1、简单易用:
- 1.2、灵活性高:
- 1.3、线程安全:
- 2. 缺点:
- 2.1、只能倒数一次:
- 2.2、不支持中断:
- 2.3、异常处理困难:
- 3. 小结:
- 五、底层原理分析
- 1. await()方法:
- 2. countDown()方法:
一、CountDownLatch简介
- CountDownLatch是一个线程同步工具类,它可以让一个或多个线程等待其他线程完成某个任务后再继续执行。它通过一个计数器来实现,计数器的初始值可以设置为一个正整数,每当一个线程完成任务后,计数器的值就会减1,当计数器的值变为0时,所有等待的线程就会被唤醒。
二、CountDownLatch类的继承关系
1. AbstractQueuedSynchronizer: 用于构建锁和同步器的框架。
AbstractQueuedSynchronizer是一个用于构建锁和同步器的基类,它提供了一个框架,供子类重写并实现自己的同步逻辑。AQS内部维护了一个FIFO的等待队列,用于管理等待获取锁的线程,并提供了一些方法供子类操作这个队列。通过继承AQS,可以方便地实现各种同步机制。
2. Sync: CountDownLatch的内部类,提供了锁的具体实现。
Sync是CountDownLatch的内部类,它继承自AbstractQueuedSynchronizer(AQS),用于实现CountDownLatch的同步机制。Sync通过继承AQS,实现了AQS的共享模式,来控制多个线程之间的同步。
三、Semaphore的基本使用
1. 使用场景:
CountDownLatch 是 Java 中的一个同步工具类,可以用来实现线程之间的等待。在玩王者荣耀时,如果需要等待队友都准备好后才能开始游戏,可以使用 CountDownLatch 来实现。
2. 代码实现:
-
首先,创建一个 CountDownLatch 对象,将其计数器初始化为队友的数量。每当一个队友准备好后,调用 CountDownLatch 的 countDown() 方法来减少计数器的值。
-
主线程调用 CountDownLatch 的 await() 方法,将会阻塞在这里,直到计数器的值为 0,即所有队友都准备好了。
-
以下是一个简单的示例代码:
import java.util.concurrent.CountDownLatch;
public class PlayGame {
public static void main(String[] args) throws InterruptedException {
int numOfTeammates = 5; // 队友的数量
// 创建一个 CountDownLatch 对象,计数器初始值为队友的数量
CountDownLatch latch = new CountDownLatch(numOfTeammates);
// 模拟队友准备的过程
for (int i = 0; i < numOfTeammates; i++) {
new Thread(() -> {
try {
// 模拟队友准备的时间
Thread.sleep((long) (Math.random() * 5000));
System.out.println(Thread.currentThread().getName() + " 准备好了");
// 队友准备好后,调用 countDown() 方法减少计数器的值
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("等待所有队友准备好...");
// 主线程调用 await() 方法等待计数器的值为 0
latch.await();
System.out.println("所有队友都准备好了,开始游戏!");
}
}
3. 运行结果:
4. 案例分析:
-
在上面的示例代码中,首先创建了一个 CountDownLatch 对象,并将计数器初始化为队友的数量。然后,使用多线程模拟队友准备的过程,每个线程准备好后调用 countDown() 方法减少计数器的值。
-
最后,主线程调用 await() 方法等待计数器的值为 0,即所有队友都准备好了,然后输出相应的提示信息,表示可以开始游戏。
-
注意:CountDownLatch 是一次性的,计数器的值减少到 0 后,无法再次使用。如果需要多次等待,可以考虑使用 CyclicBarrier 类。
四、CountDownLatch的优缺点
1. 优点:
CountDownLatch 是 Java 并发包中的一个工具类,用于控制多个线程的执行顺序。它的主要优点是:
1.1、简单易用:
CountDownLatch 提供了一种简单的机制,可以让一个或多个线程等待其他线程完成某个操作后再继续执行。它的使用方法非常简单,只需在需要等待的线程中调用 await() 方法即可。
1.2、灵活性高:
CountDownLatch 可以用于任意数量的线程等待,而且可以在任意时刻进行倒数计数,不需要事先确定等待线程的数量。
1.3、线程安全:
CountDownLatch 内部使用了多线程并发控制的机制,保证了线程安全性。
2. 缺点:
然而,CountDownLatch 也存在一些缺点:
2.1、只能倒数一次:
CountDownLatch 的计数器只能倒数一次,一旦计数器归零,就无法重置。如果需要重复使用倒数计数功能,就需要重新创建一个新的 CountDownLatch。
2.2、不支持中断:
CountDownLatch 的 await() 方法在等待时无法被中断,只能等倒数计数完成或者等待超时才能退出。
2.3、异常处理困难:
在使用 CountDownLatch 时,如果等待的线程发生异常导致无法完成计数操作,其他线程将一直等待下去,没有异常处理机制。
3. 小结:
总体来说,CountDownLatch 是一种简单有效的线程同步工具,但在某些特定情况下可能不够灵活,需要结合其他并发工具来解决问题。
五、底层原理分析
CountDownLatch是Java多线程中的一个同步工具类,它可以让一个或多个线程等待其他线程完成操作后再继续执行。CountDownLatch类提供了两个重要的方法:await()和countDown()。
1. await()方法:
- await()方法用于使当前线程等待,直到计数器的值变为0。
- 在调用await()方法之前,需要通过构造函数指定计数器的初始值。
- 当一个线程调用await()方法后,如果计数器的值大于0,则该线程会被阻塞,直到计数器的值减到0为止。
- 当计数器的值为0时,await()方法将立即返回,并且所有等待的线程将被唤醒。
2. countDown()方法:
- countDown()方法用于将计数器的值减1。
- 每次调用countDown()方法,计数器的值都会减1。
- 当计数器的值减到0时,await()方法中阻塞的线程将被唤醒。
- 下面是CountDownLatch的部分源码分析:
public class CountDownLatch {
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 void countDown() {
sync.releaseShared(1);
}
private static final class Sync extends AbstractQueuedSynchronizer {
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;
}
}
}
}
-
在CountDownLatch的源码中,Sync是一个继承了AbstractQueuedSynchronizer的内部类,它实现了具体的计数逻辑。在Sync中,使用了一个整数来表示计数器的值。在构造函数中,通过setState(count)来初始化计数器的值。tryAcquireShared()方法用于判断计数器是否已经减到0,如果是则返回1,表示可以获取共享资源;如果不是则返回-1,表示无法获取共享资源。tryReleaseShared()方法用于减少计数器的值,并判断是否减到0,如果是则返回true,表示可以唤醒等待的线程;如果不是则返回false。
-
在await()方法中,调用了sync.acquireSharedInterruptibly(1)方法,该方法会调用Sync中的tryAcquireShared()方法。如果计数器的值不为0,则当前线程会被阻塞,直到计数器的值减到0为止。
-
在countDown()方法中,调用了sync.releaseShared(1)方法,该方法会调用Sync中的tryReleaseShared()方法。每次调用countDown()方法,计数器的值都会减1。当计数器的值减到0时,tryReleaseShared()方法会返回true,表示可以唤醒等待的线程。
-
通过上述分析,我们可以看出CountDownLatch的await()方法和countDown()方法是通过对计数器的操作来实现线程的等待和唤醒。当计数器的值减到0时,await()方法中阻塞的线程将被唤醒,继续执行后续的操作。