一 使用场景
在网上找了个例子,这个例子很好地说明了 CountDownLatch 的用法,概括地说就是主线程等待子线程执行完了,主线程接着执行
public class CountDownLatchTest { public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(2); System.out.println("主线程开始执行…… ……"); //第一个子线程执行 ExecutorService es1 = Executors.newSingleThreadExecutor(); es1.execute(new Runnable() { @Override public void run() { try { Thread.sleep(3000); System.out.println("子线程:"+Thread.currentThread().getName()+"执行"); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown(); } }); es1.shutdown(); //第二个子线程执行 ExecutorService es2 = Executors.newSingleThreadExecutor(); es2.execute(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程:"+Thread.currentThread().getName()+"执行"); latch.countDown(); } }); es2.shutdown(); System.out.println("等待两个线程执行完毕…… ……"); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("两个子线程都执行完毕,继续执行主线程"); } }
二 代码概况
CountDownLatch的功能都是通过内部实现AQS来实现的
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; } } } private final Sync sync;
CountDownLatch的构造方法如下
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
用户传进去的count就赋值给AQS的state
三 await源码分析
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
之前分析过共享锁的代码,共享锁的主要逻辑还是要看用户实现的 tryAcquireShared
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
如果state不是0的话,返回-1,这样主线程就会进到队列中排队,并把自己阻塞起来,只能等待其他线程的唤醒
四 CountDown源码分析
每次调用 countDown() 固定是释放一个共享资源
public void countDown() { sync.releaseShared(1); }
protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0)//如果已经是0,那就没法释放了,返回false,这样就不会执行unpark方法 return false; int nextc = c-1; if (compareAndSetState(c, nextc))//减一成功后比较state是不是0,如果返回true return nextc == 0; } }
如果state == 0,那么 tryReleaseShared 返回true,这样就会执行unpark的逻辑,这样主线程被唤醒,代码就可以继续执行了
五 总结
CountDownLatch的源码不多,逻辑也不复杂,但是可以很好的看看如果通过继承AQS实现自己的逻辑。