Java中的CountDownLatch源码解析
作者:正经人z.
一、简介
1、CountDownLatch类是一个同步辅助装置,允许一个或多个线程去等待直到另外的线程完成了一组操作。
2、它通过count进行初始化,await方法会阻塞直到当前的count为0由于调用了countDown方法,之后所有的线程将被释放并且立即返回结果。count不能被重置,如果你想count可以重置,请使用CyclicBarrier。
3、CountDownLatch是一个通用的同步工具,可用于多种用途。CountDownLatch初始化使用count作为一个简单的可开可关的大门:所有的线程调用await方法等待在大门里,当一个线程调用了countDown方法后大门打开
二、源码分析
public class CountDownLatch { //内部类Sync继承了AQS private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; //构造方法中的count其实就是传给了AQS的state属性 Sync(int count) { setState(count); } //得到的AQS的state属性值 int getCount() { return getState(); } //重写的AQS的tryAcquireShared,在共享模式的情况下获取锁 protected int tryAcquireShared(int acquires) { //获取锁的前提是state为0,表示当前未被其他线程占有 return (getState() == 0) ? 1 : -1; } //重写的AQS的tryReleaseShared,在共享模式下释放锁 protected boolean tryReleaseShared(int releases) { // 减count; 当count为0时唤醒 for (;;) { int c = getState(); if (c == 0) //表示释放锁的前提是占有锁,也就是state的属性值大于0 return false; int nextc = c-1; //state的值减1 if (compareAndSetState(c, nextc)) //利用CAS来改变state的值 return nextc == 0; //当state的值为0时返回true } } } private final Sync sync; /** * 通过count初始化一个CountDownLatch * *在线程调用await方法后如果想通过,必须执行count次countDown方法 * @如果count为负数,则抛出IllegalArgumentException */ public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } ================================================================================================ 两个核心方法,其实底层使用的AQS的方法(共享模式下) /** * 调用await方法会使当前的线程等待除非count的数值降为0或者中间抛出异常 * * 如果当前的count为0,则该方法立即返回 * *如果当前的count大于0,则当前线程因线程调度目的而被禁用,并且休眠,直到发生以下两种情况之一: *1、调用countDown方法将count的值降为0 *2、其他的线程打断了当前线程,使用Thread.interrupted */ public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } /** *减count的值,如果count的值为0了,则释放所有等待的线程 * *如果当前的count值大于0,那么它是被减了。 *如果当前的count值等于0,那么所有等待的线程被唤醒接受线程的调度 */ public void countDown() { sync.releaseShared(1); } ================================================================================================ /** *返回count的值,用于调试或者测试 */ public long getCount() { return sync.getCount(); }
三、小练习-模拟王者荣耀单挑
public static void main(String[] args) throws Exception{ CountDownLatch cd = new CountDownLatch(3); Thread beginGame = new Thread(new Runnable() { @Override public void run() { try{ cd.await(); System.out.println("欢迎来到王者荣耀,敌军还有30秒到达战场============"); }catch(Exception e){ e.printStackTrace(); } } },"beginGame"); Thread player1 = new Thread(new Runnable() { @Override public void run() { try{ System.out.println("玩家一以准备======="); cd.countDown(); }catch(Exception e){ e.printStackTrace(); } } },"player1"); Thread player2 = new Thread(new Runnable() { @Override public void run() { try{ System.out.println("玩家二以准备======="); cd.countDown(); }catch(Exception e){ e.printStackTrace(); } } },"player2"); beginGame.start(); player1.start(); player2.start(); }
四、总结
1、请注意,只有在共享模式下才能使用CountDownLatch,因为只有在共享模式下,AQS的state属性的值才有可能大于1,才有后续的等待state的值为0,其他的线程才能被唤醒继续执行的可能,这里针对的多个线程等待!
2、CountDownLatch里面有两个核心方法:await和countDown。这里注意,await和平时学习的Condition中的await不一样,这里的await就是线程获取锁且必须在state值为0,也就是该资源未被占有的情况下,获取锁成功后state的值从0变为1,表示该线程持有了该资源的锁。countDown就是将state的属性值减1。这两个方法其实都是调用的AQS的方法执行的。
3、初始化传入的count表示的就是AQS的state属性的值,可以理解为持有该资源的线程数,其他的线程想要拿到这个资源,必须等到该资源的state的值变为0才能被唤醒去获取锁。也说明了如果该state的值要从N变为0,需要执行N次countDown方法
4、有点类似于线程通信机制的wait/notify。
5、CountDownLatch典型的用法是将一个程序分为n个互相独立的可解决任务,并创建值为n的CountDownLatch。当每一个任务完成时,都会在这个锁存器上调用countDown,等待问题被解决的任务调用这个锁存器的await,将他们自己拦住,直至锁存器计数结束。
6、个人理解:线程A和线程B争夺一个资源,且线程B需要在线程A之后才能执行,线程A首先拿到这个资源,然后线程A需要执行一个大任务,这个大任务可以分解为N个小任务,所以,创建一个N的count的CountDownLatch,所以,state的值就是N了,线程A每执行完一个小任务后,将调用CountDownLatch的countDown方法将state的值减1,直到最后,所有的小任务执行完毕,代表线程A的大任务也就执行完毕了,此时,该资源的state的值为0了,代表可以被其他线程争夺获取锁。线程B就可以调用countDown的await就能够获取该资源的锁,并将state的值变为1。
到此这篇关于Java中的CountDownLatch源码解析的文章就介绍到这了,更多相关CountDownLatch源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!