类介绍
CountDownLatch是JDK提供的一个同步工具,可用于多种用途。 初始化为1的CountDownLatch用作简单的开/关锁存器或门:所有调用await的线程在门处等待,直到调用countDown()的线程打开它。 初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程完成某个操作,或者某个操作已完成N次。
构造方法
构造方法只有一个,且只有一个参数count,count作用相当于上面类介绍的N,其构造方法的源码如下
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch有一个内部类叫做Sync,它继承了AbstractQueuedSynchronizer类。创建CountDownLatch实例时,也会创建一个Sync的实例,同时把计数器的值传给Sync实例
常用方法
下面我们来看countDown()方法的源码,具体如下
public void countDown() {
sync.releaseShared(1);
}
在 countDown()方法中,只调用了Sync实例的releaseShared()方法,具体如下
/**
* Releases in shared mode. Implemented by unblocking one or more
* threads if {@link #tryReleaseShared} returns true.
* 以共享模式发布。如果{@link #tryReleaseShared}返回true,则通过取消阻塞一个或多个线程来实现。
* @param arg the release argument. This value is conveyed to
* {@link #tryReleaseShared} but is otherwise uninterpreted
* and can represent anything you like.
* arg释放参数。该值被传递给{@link#tryReleaseShared},但在其他方面是不可解释的,并且可以表示您喜欢的任何内容。
* @return the value returned from {@link #tryReleaseShared}
* 从{@link #tryReleaseShared}返回的值
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//对计数器减一
signalNext(head);如果计数器为0时,就唤醒被await()阻塞的线程
return true;
}
return false;
}
其中的releaseShared()方法,先对计数器进行减1,如果减后的计数器为0时,唤醒被await()的所有线程,并直接返回true。其JDK所提供的源码如下
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {//死循环,作用跟while(true)差不多
//获取计数器的值
int c = getState();
//如果计数器为0时,就直接返回
if (c == 0)
return false;
int nextc = c - 1;
//使用方法对计数器减一
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
接下来我们来await()方法的源码
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
其中await()只调用Sync实例的acquireSharedInterruptibly(1)的方法,其acquireSharedInterruptibly(1)的源码如下
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判断线程是否被中断
if (Thread.interrupted())
throw new InterruptedException();
/*判断计数器是否等于0,其中tryAcquireShared(arg)方法里主要运用了一个三目运算符,
如果里面的值等于0,就返回1,否则返回-1
*/
if (tryAcquireShared(arg) < 0)
//如果计数器不等0,就阻塞阻塞当前线程
doAcquireSharedInterruptibly(arg);
}
其中tryAcquireShared()方法,是AbstractQueuedSynchronizer(可以看成一个实现锁和需要同步功能的队列)中的一个模板方法,其作用主要是判断计数器是否为零,如果为零则返回1,如果不为零则返回-1,
protected int tryAcquireShared(int acquires) {
//使用了三目运算符,如果等于0,就返回1,否则返回-1
return (getState() == 0) ? 1 : -1;
}
其中getState()主要作用时获取当前计数器的值,具体如下
protected final int getState() {
return state;
}
例子
代码
下面使用代码模拟一下全部学生晚自习结束,然后离开教室,最后值班保安关门
package testJUC;
import java.util.concurrent.CountDownLatch;
//减法计数器
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
//假设有40个学生
CountDownLatch countDownLatch = new CountDownLatch(40);
for (int i = 1; i <= 40; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "晚自习完成,已经离开教室");
//计数器减1
countDownLatch.countDown();
}, "学号为"+i+"同学").start();
}
//等待计数器减至为零,然后向下执行
countDownLatch.await();
System.out.println("全部同学离开");
System.out.println("值班保安关门");
}
}
运行结果
学号为7同学晚自习完成,已经离开教室
学号为1同学晚自习完成,已经离开教室
学号为3同学晚自习完成,已经离开教室
学号为4同学晚自习完成,已经离开教室
学号为5同学晚自习完成,已经离开教室
学号为2同学晚自习完成,已经离开教室
学号为8同学晚自习完成,已经离开教室
学号为6同学晚自习完成,已经离开教室
学号为10同学晚自习完成,已经离开教室
学号为12同学晚自习完成,已经离开教室
学号为13同学晚自习完成,已经离开教室
学号为11同学晚自习完成,已经离开教室
学号为16同学晚自习完成,已经离开教室
学号为9同学晚自习完成,已经离开教室
学号为18同学晚自习完成,已经离开教室
学号为22同学晚自习完成,已经离开教室
学号为19同学晚自习完成,已经离开教室
学号为26同学晚自习完成,已经离开教室
学号为30同学晚自习完成,已经离开教室
学号为23同学晚自习完成,已经离开教室
学号为21同学晚自习完成,已经离开教室
学号为27同学晚自习完成,已经离开教室
学号为24同学晚自习完成,已经离开教室
学号为34同学晚自习完成,已经离开教室
学号为15同学晚自习完成,已经离开教室
学号为14同学晚自习完成,已经离开教室
学号为31同学晚自习完成,已经离开教室
学号为17同学晚自习完成,已经离开教室
学号为25同学晚自习完成,已经离开教室
学号为29同学晚自习完成,已经离开教室
学号为32同学晚自习完成,已经离开教室
学号为20同学晚自习完成,已经离开教室
学号为33同学晚自习完成,已经离开教室
学号为28同学晚自习完成,已经离开教室
学号为35同学晚自习完成,已经离开教室
学号为38同学晚自习完成,已经离开教室
学号为37同学晚自习完成,已经离开教室
学号为36同学晚自习完成,已经离开教室
学号为39同学晚自习完成,已经离开教室
学号为40同学晚自习完成,已经离开教室
全部同学离开
值班保安关门