0 概述
闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行。CountDownLatch是一种灵活的闭锁实现,它可以使一个或者多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器初始化一个正数,表示需要等待的数量。countDown方法递减计数器,表示一个事件已经发生了,而await方法等待计算器达到零,即所有的事件已经完成。如果计数器的值非0,await会一直等待直到计数器为0,或者线程被中断或者超时。
1 使用实例
import java.util.concurrent.CountDownLatch;
/**
* Created by apple on 17/10/24.
*/
public class CountDownLatchTest {
public static void main(String[] args) throws Exception {
//参数count就是事件数(线程数量、计数器)
final CountDownLatch countDownLatch = new CountDownLatch(2);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
sleepWithNoException(3000);
System.out.println("任务1 已经完成");
//count-1
countDownLatch.countDown();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
sleepWithNoException(1000);
System.out.println("任务2 已经完成");
//count-1
countDownLatch.countDown();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//阻塞等待
countDownLatch.await();
System.out.println("thread3 over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread3.start();
thread2.start();
thread1.start();
//阻塞等待
countDownLatch.await();
System.out.println("任务都已经完成");
}
private static void sleepWithNoException(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
任务2 已经完成
任务1 已经完成
thread3 over
任务都已经完成
2 CountDownLatch 设计思想
CountDownLatch 是基于AbstractQueuedSynchronizer实现的,AbstractQueuedSynchronizer 中维护了一个队列(双向链表实现),其中结点中除了存放连接前后结点指针外还存放线程信息、线程状态等信息,值得说明是head node 是一个虚节点(只是起到一个连接作用)。
那么问题来了,线程是如何阻塞以及唤醒的呢?
这里要提出LockSupport类,其有两个重要的方法:
//唤醒线程(底层实现依赖于操作系统)
public static void unpark(Thread thread);
//阻塞当前线程(底层实现依赖于操作系统)
public static void park(Object blocker)
值得说明的是:unpark函数可以先于park调用。unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”,如果已经有许可,那么线程会马上再继续运行。
public class ParkTest {
public static void main(String[] args) {
LockSupport.unpark(Thread.currentThread());
LockSupport.park(Thread.currentThread());
System.out.println("线程可以继续执行");
}
}
//程序会直接运行到结束,不会阻塞
3 CountDownLatch 源码分析
内部类&构造函数
//继承AbstractQueuedSynchronizer的内部类
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;
}
//基于CAS原子尝试释放共享锁,当线程原子更新为state为0成功时候返回true,其余情况返回false
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;
}
}
}
// 构造函数
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch类await() ,下面结合源码来分析下是如何阻塞等待线程的
//await() 方法具体实现
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//判断state是否为0 ,如果为0就不用阻塞当前线程了
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//在队列尾部添加一个新的node(如果首次添加,则会先创建一个头结点然后在添加)
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
//r>=0 表示count=0即等待的线程已经结束
if (r >= 0) {
//重新设置头结点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//设置node状态设置为SIGNAL=-1,并阻塞当前线程(这里由于unpark 可以有先于park 执行,因此不会有并发问题)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
CountDownLatch类countDown() 方法实现
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//由于是共享锁,那么release 动作将会传播至各个阻塞节点
doReleaseShared();
return true;
}
return false;
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒后继节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果头结点变了,则不退出循环,唤醒线程后会重新设置头节点
if (h == head) // loop if head changed
break;
}
}