CountDownLatch是java并发包中辅助并发的工具类,目的是让并发运行的代码在某一个执行点阻塞,直到所有条件都满足,这里的条件就是调用countDown()方法,有点类似计数器的功能。
用法如
public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); //如果没有countDown()操作,直接调用await方法则永远不会打印最后一行 countDownLatch.countDown(); countDownLatch.countDown(); countDownLatch.await(); System.out.println("运行结束"); }
构造函数中传入的数字2,表示需要2次countDown()方法调用,否则代码会一直阻塞在await()方法调用处
比较常见的用法如,主线程声明一个CountDownLatch,然后多线程countDown,主线程在等待
1 public static void testMultiThread() throws InterruptedException { 2 int threadNum = 2; 3 final CountDownLatch countDownLatch = new CountDownLatch(threadNum); 4 ExecutorService executorService = Executors.newFixedThreadPool(threadNum); 5 for(int i=0;i<threadNum;i++){ 6 executorService.execute(new Runnable() { 7 public void run() { 8 System.out.println("我的任务,打印出一句话"); 9 countDownLatch.countDown(); 10 } 11 }); 12 } 13 countDownLatch.await(); 14 System.out.println("全部任务都结束了,欧耶"); 15 executorService.shutdown(); 16 }
运行结果
我的任务,打印出一句话 我的任务,打印出一句话 全部任务都结束了,欧耶 Process finished with exit code 0
如果把第三行代码修改成
final CountDownLatch countDownLatch = new CountDownLatch(threadNum+1);
那么程序将永远无法打印出
全部任务都结束了,欧耶
以上是这个类的表象行为,那么它是如何在多线程做到这样的功能呢
先来看看它的类结构
CountDownLacth中,有6个public的方法,一个内部私有类Sync,及一个Sync实例的变量sync
Sync是这个类的关键,它保证了countDown(),await()方法在多线程场景下可以保证countDownLatch的可见性(正常的同步)
我们先来自己实现一个CountDownLacth类,使用synchronized关键字实现
MyCountDownLatch模拟了countDown和await方法,通过synchronized和私有变量state来达到这个目的。synchronized的劣势在于锁机制完全互斥,并发量高时性能下降比较明显,无法维持常态化的性能(JDK 5)。因此CountDownLatch以及并发包中的类都采用了取巧的方式,通过线程自旋来追求线程响应时间,而不是让线程只能一直等待锁被释放再竞争。1.6之后,synchronized的性能和ReentrantLock的性能其实已经相当,偏向锁也改进了一个线程重复获取锁时不需要cpu切换上下文。
private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L;
//初始化总的countdown次数值,这个操作涉及内存语义 volatile写 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; } } }
CountDownLatchd countDown方法会调用
Sync的tryReleaseShared去将计数器-1
public void countDown() { sync.releaseShared(1); }
AbstractQueuedSynchronizer类方法
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } AbstractQueuedSynchronizer类方法 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } AbstractQueuedSynchronizer类方法 /** * Acquires in shared interruptible mode. * @param arg the acquire argument */ 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); } }
await()方法Sync的tryAcquireShared方法不断判断计数器值为0,方法返回正数,await方法直接返回,亦即是获取了锁
AbstractQueuedSynchronizer类是concurrent包的一个基础类,基于它,我们可以实现并发安全的诸多功能,例如CountDownLacth
/** * The synchronization state. */ private volatile int state;
AbstractQueuedSynchronizer类有一个volatile的变量,CountDownLacth中的Sync类,getState()方法其实就是获取它的值
由于volatile关键字的特殊内存语义,当它被修改时,将本地高速缓存中的值写到主存中去,当它的值被读取时,会把本地缓存中state置为无效,到主存获取值,因此每个线程都能获取到他最新的值
countDownLatch.countDown(); -> sync.releaseShared(1); -> public final boolean releaseShared(int arg) { //countdown时,尝试判断当前状态,如果状态已经可以完全释放锁时,进行释放锁操作 if (tryReleaseShared(arg)) { doReleaseShared(); //释放共享锁 return true; } return false; } /** * Release action for shared mode -- signals successor and ensures * propagation. (Note: For exclusive mode, release just amounts * to calling unparkSuccessor of head if it needs signal.) */ private void doReleaseShared() { /* 判断是否已经有线程(node)在等待,如果在等待,则从head开始,寻找后继节点,并唤醒他们 * 直到最后head 为null或head == tail,才退出,退出时,已然唤醒了所有需要唤醒的线程 * 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) { //head == tail 时,是一个特殊情况,是第一个node入队时的临时状态,此时head节点上没有等待的线程 int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); //head唤醒后继线程 } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) //尝试设置waitStatus状态为PROPAGATE continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
countDownLatch.await(); -> sync.acquireSharedInterruptibly(1); -> /** * Acquires in shared mode, aborting if interrupted. Implemented * by first checking interrupt status, then invoking at least once * {@link #tryAcquireShared}, returning on success. Otherwise the * thread is queued, possibly repeatedly blocking and unblocking, * invoking {@link #tryAcquireShared} until success or the thread * is interrupted. * @param arg the acquire argument. * This value is conveyed to {@link #tryAcquireShared} but is * otherwise uninterpreted and can represent anything * you like. * @throws InterruptedException if the current thread is interrupted */ public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) //尝试判断共享锁状态,即判断state是否为0了,如果已经为0,表示期待的状态已经达到了,锁的状态已经标识不需等待了,直接返回 doAcquireSharedInterruptibly(arg); //为0后 } /** * Acquires in shared interruptible mode. * @param arg the acquire argument */ private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); //当前线程装入node,此时等待队列不为空了 boolean failed = true; try { for (;;) { final Node p = node.predecessor(); //当前线程的前继线程 if (p == head) { //前继为head int r = tryAcquireShared(arg); //判断state状态,是否已经ok if (r >= 0) { //state值为0时,r == 1 setHeadAndPropagate(node, r); //当前node设置为head,尝试获取下一个node p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) //尝试park(阻塞)当前线程,有点像进入wait状态,处理器可能不会分配时间 throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
这里需要说明一下,node的waitStatus是在何时被设置为Node.SIGNAL状态的
shouldParkAfterFailedAcquire方法中,pred参数为当前node的前继node,方法一进来就判断了pred.waitStatus是否为Node.SIGNAL,如果已经是这个状态了就直接返回
ws>0表示CANCELLED状态,因为其他状态都是<=0的,此时遍历的将这种无效的node去掉;如果ws <= 0,就将pred的状态变成Node.SIGNAL,并返回false;这里可以看到,如果前缀node已经是SIGNAL状态
就会park当前节点线程,如果,前缀node是未cancel状态,就设置为SIGNAL状态。这样,在调用await时,如果无法立即返回,就会将当前线程阻塞,并设置前置node状态为SIGNAL。这也对应了releaseShared
中的Node.SIGAL状态的判断。await()是从tail -> head方向做的;countdown(releaseShared)的方向是从head -> tail的,这样,等待锁的线程不断判断前缀node,释放锁的线程不断更新head -> tail
状态,随着head -> tail状态更新完毕,await等待锁也等到了p == head,返回true
/** * Checks and updates status for a node that failed to acquire. * Returns true if thread should block. This is the main signal * control in all acquire loops. Requires that pred == node.prev. * * @param pred node's predecessor holding status * @param node the node * @return {@code true} if thread should block */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
Java的LockSupport.park()实现分析 参见博客: https://blog.csdn.net/hengyunabc/article/details/28126139
附waitStatus的各种状态 /** * Status field, taking on only the values: * SIGNAL: The successor of this node is (or will soon be) * blocked (via park), so the current node must * unpark its successor when it releases or * cancels. To avoid races, acquire methods must * first indicate they need a signal, * then retry the atomic acquire, and then, * on failure, block. 线程的后继线程正/已被阻塞,当该线程release或cancel时,要唤醒这个后继线程(unpark) * CANCELLED: This node is cancelled due to timeout or interrupt. * Nodes never leave this state. In particular, * a thread with cancelled node never again blocks. 由于timeout或线程被中断时的状态 * CONDITION: This node is currently on a condition queue. * It will not be used as a sync queue node * until transferred, at which time the status * will be set to 0. (Use of this value here has * nothing to do with the other uses of the * field, but simplifies mechanics.) 表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞 * PROPAGATE: A releaseShared should be propagated to other * nodes. This is set (for head node only) in * doReleaseShared to ensure propagation * continues, even if other operations have * since intervened. 传播共享锁,只能在head上设置这个状态 * 0: None of the above * * The values are arranged numerically to simplify use. * Non-negative values mean that a node doesn't need to * signal. So, most code doesn't need to check for particular * values, just for sign. * * The field is initialized to 0 for normal sync nodes, and * CONDITION for condition nodes. It is modified using CAS * (or when possible, unconditional volatile writes). */ volatile int waitStatus;