CountDownLatch的作用很简单,就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以。 我们举一个例子来说明,在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。
简而言之,CountDownLatch有一个计数器字段,您可以根据需要减少它。然后我们可以用它来阻塞一个调用线程,直到它被计数到零。
public class CountDownLatchTest {
public static CountDownLatch countDownLatch = new CountDownLatch(7);
public static void main(String[] args) {
for (int i = 0; i < 7; i++) {
int index = i;
new Thread(() -> {
System.out.println("第"+(index+1)+"个人已经到达");
countDownLatch.countDown(); //countDownLatch计数器减一
}).start();
}
try {
countDownLatch.await(); //将主线程阻塞,直到其他线程执行完毕,countDownLatch计数器为0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有人都已经到达");
}
}
如果上面的程序不使用countDownLatch会得到什么结果?
public class CountDownLatchTest {
public static void main(String[] args) {
for (int i = 0; i < 7; i++) {
int index = i;
new Thread(() -> {
System.out.println("第"+(index+1)+"个人已经到达");
}).start();
}
System.out.println("所有人都已经到达");
}
}
输出:
如果不使用countDownLatch就有可能会使主线程先执行,再执行其他线程。所以countDownLatch通常用于保证某一线程在其他线程执行完毕,再运行当前线程。
public void countDown() {
sync.releaseShared(1);
}
CountDownLatch里面保存了一个count值,通过减1操作,直到为0时候,等待线程才可以执行。调用了sync的releaseShared方法进行减一。
sync是什么,releaseShared方法又是如何实现的?
sync继承了AbstractQueuedSynchronizer(AQS)。AQS的其中一个作用就是维护线程状态和获取释放锁。在这里也就是说CountDownLatch使用AQS机制维护锁状态。而releaseShared(1)方法就是释放了一个共享锁。底层使用AQS机制调用releaseShared方法释放一个锁资源。
两个方法都是让线程等待,一个没有时间限制,一个有时间限制
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
(1)无时间限制的 await方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
(2)有时间限制的 wait方法
等待指定的时间,如果还有线程没执行完,那就接着执行。
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
java中还有一个同步工具类叫做CyclicBarrier,他的作用和CountDownLatch类似。同样是等待其他线程都完成了,才可以进行下一步操作。
我们看一下区别:
CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
关键点其实就在于那N个线程
(1)CountDownLatch里面N个线程就是学生,学生做完了试卷就可以走了,不用等待其他的学生是否完成
(2)CyclicBarrier 里面N个线程就是所有的游戏玩家,一个游戏玩家加载到100%还不可以,必须要等到其他的游戏玩家都加载到100%才可以开局
CountDownLatch的缺点?
CountDownLatch看起来虽然很好用,也有很多不足之处,比如说CountDownLatch是一次性的 , 计数器的值只能在构造方法中初始化一次 , 之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后 , 它不能再次被使用。