面试阿里,源码分析CountDownLatch,面试官被我折服,offer到手

172 阅读3分钟

面试官:

小伙砸,听说你熟读JUC源码啊,今天来考考你CountDownLatch(心想:哼哼,必须难倒这小子,不能让他活着离开)

 

我:

 好的,没问题。(哼哼,想难住我,让您老失望了)\

 

面试官:

 请开始你的表演\

 

我:


一、CountDownLatch重要方法

// 构造函数,count代表计数器个数(内部是共享锁,本质就是上了几次锁)
一、public CountDownLatch(int count)


// 每countDown一次,计数器就减一,就是释放了一次共享锁,直到为0全部结束
二、public void countDown()


// 在AQS队列里一直等待,直到countDown减到0,才会继续往下执行
三、public void await()

二、重要的内部类Sync

// 继承了AQS
private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;




        Sync(int count) {
            setState(count);
        }




        int getCount() {
            return getState();
        }




        // await判断共享锁是否全部释放,是则从队列中移除,继续往下执行,实现AQS的模板方法
        protected int tryAcquireShared(int acquires) {
            // 判断state同步锁状态是否全部释放
            // 全部释放就可以继续执行被await的代码了
            return (getState() == 0) ? 1 : -1;
        }




        // countDown释放共享锁,实现AQS的模板方法
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) { // 自旋不断判断是否释放了state同步锁
                int c = getState();
                // 如果一进for循环state就是0,说明之前已经释放了同步锁,
                // 这时候就不需要做任何操作了,因为之前已经做完了
                if (c == 0) 
                    return false;
                // state - 1释放一次同步锁
                int nextc = c-1;
                // 通过CAS设置state同步状态,如果成功判断state是否为0,为0代表锁全部释放
                // 被await的程序可以继续往下执行了
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

三、案例举例

示例使用开会案例。老板进入会议室等待5个人全部到达会议室才会开会。
所以这里有两个线程老板等待开会线程、员工到达会议室:
public class CountDownLatchTest {
    private static CountDownLatch countDownLatch = new CountDownLatch(5);


    /**
     * Boss线程,等待员工到达开会
     */
    static class BossThread extends Thread{
        @Override
        public void run() {
            System.out.println("Boss在会议室等待,总共有" + countDownLatch.getCount() + "个人开会...");
            try {
                //Boss等待
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("所有人都已经到齐了,开会吧...");
        }
    }


    //员工到达会议室
    static class EmpleoyeeThread  extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ",到达会议室....");
            //员工到达会议室 count - 1
            countDownLatch.countDown();
        }
    }


    public static void main(String[] args){
        //Boss线程启动
        new BossThread().start();


        for(int i = 0,j = countDownLatch.getCount() ; i < j ; i++){
            new EmpleoyeeThread().start();
        }
    }
}

四、CountDownLatch构造方法

(AQS不懂请看这篇文章http://mp.weixin.qq.com/s?__biz=MzU0NDA2MjY5Ng==&mid=100000391&idx=1&sn=83f4bad784040958361560a92448a7fd&chksm=7b00af004c772616aa1f5df73b756ce1720ba5d092f65d4f72a1f69308199956098a705ec42a#rd)

创建CountDownLatch时通过此构造方法传入需要上共享锁几次,通过sync这个对象操作AQS的state同步锁状态

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
 
五、countDown方法  

调用sync.releaseShared,其实是调用AQS里的releaseShared,sync只是去实现了AQS里的tryReleaseShared,去自己控制如何释放state同步锁状态

public void countDown() {
    sync.releaseShared(1);
}

六、await方法

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

 

七、总结

1、有了分析AQS的基础后,再来分析CountDownLatch便快了很多;
   其实CountDownLatch就是利用了AQS的共享锁来实现的这些功能
   理解了AQS的共享锁,也就理解了CountDownLatch

2、在这里我简要总结一下CountDownLatch的流程的一些特性:
    • 管理一个大于零的计数器值;
    • 每countDown一次则state就减1一次,直到许可证数量等于0则释放队列中所有的等待线程

 

面试官脸色铁青:

唉,没难住这小子, 只能把offer发给他了\

 

我:

 offer到手,天下我有

给个[在看],是对IT老哥最大的支持