在看这篇博客之前,建议大家将AQS源码理清楚再来看。当然,我之前的博客中有写AQS。若你将AQS没搞懂,这里看起来就不容易理解,不然你看CountDownLatch的源码将很简单
CountDownLatch是通过一个计数器来实现的,当我们在new 一个CountDownLatch对象的时候需要带入该计数器值,该值就表示了线程的数量。每当一个线程完成自己的任务后,计数器的值就会减1。当计数器的值变为0时,就表示所有的线程均已经完成了任务,然后就可以恢复等待的线程继续执行了。
一、成员变量
private final Sync sync;
在CountDownLatch源码中,只有一个成员变量Sync的对象,Sync是CountDownLatch的一个内部类,这个类将是CountDownLatch功能实现的核心。
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; } 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; } } }
可见Sync类继承与AQS,并重写了其中的两个方法tryAcquireShared和tryReleaseShared方法。就是获取资源和释放资源的方法。
1、构造方法
Sync(int count) { setState(count); }
这个构造方法就是你在构造CountDownLatch时传入的参数,也就是计数器的值。
2、操作方法
tryAcquireShared方法
这个很简单,判断计数器的值是否等于0,等于0返回1,否则返回-1。
tryReleaseShared
方法步骤如下
1、获取计数器的值
2、如果计数器值为0,返回false
3、如果计数器值不为0,则使用CAS操作将计数器值减1,如果CAS操作成功,返回当前计数器值是否等于0。
二、操作方法
1、await方法
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //调用Sync类中重写的方法 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
await方法调用了AQS的acquireSharedInterruptly(1)方法,可以看到这个方法会执行我们重写的tryAcquireShared方法
我们重写的方法就是计数器值为0返回true(说明所有线程都已完成任务),否则返回false(说明还有线程没有完成任务)
2、带计时器的await方法
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
这个方法和await方法一样,唯一的区别就是这个方法有时间限制,当线程没有在指定的时间内完成任务,await方法失效,所有线程都会通过await方法去执行未完的代码。
3、countDown方法
public void countDown() { sync.releaseShared(1); }
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
countDown方法调用了AQS的releaseShared方法。这个方法会调用我们重写的tryReleaseShared方法。
重写的tryRelease方法以死循环的方式来确认计数器的值,先将计数器减一。当计数器为0时,返回true,不然返回false。
三、CountDownLatch的应用
public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(2); Thread t1 = new Thread(){ @Override public void run() { System.out.println("线程A解析完成"); latch.countDown(); } }; Thread t2 = new Thread(){ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程B解析完成"); latch.countDown(); } }; t1.start(); t2.start(); latch.await(); System.out.println("所有线程都已经解析完成"); }
上述代码有两个线程去解析任务,只有当两个线程都完成解析,主线程才能去解析。
我们创建了计数器值为2的CountDownLatch。在主线程解析的前面调用await方法,在每个线程完成解析后调用countDown方法,这样就可以达到我们所期望的要求。