Java并发源码分析之CountDownLatch

关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之Semaphore
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier

工作原理概要

CountDownLatch是基于AQS框架实现的,在初始化时,会设置同步状态state的值,相当于一次获取了N个共享锁。主线程调用await()方法加入同步队列进行等待,然后子线程每次调用countDown()方法释放一个共享锁,直到共享锁的个数为0,表示释放锁完成,释放完成后再唤醒主线程。

类图

类图
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继AbstractOwnableSynchronizer,里面包含内部类Node
Sync:类,继承了AbstractQueuedSynchronizer,重写tryAcquireShared、tryReleaseShared方法
CountDownLatch:包含内部类Sync

使用demo

CountDownLatch适用于分步执行任务,使用多线程执行第一步,等待所有线程第一步执行完成,再执行第二步。例如,下载多个文件,使用多线程,每个线程下载一个,最后再将所有的文件打包成一个,非常适合使用countDownLatch。

 public class CountDownLatchRunnable implements Runnable{

    private CountDownLatch countDownLatch;

    public CountDownLatchRunnable(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            System.out.println("线程: " + Thread.currentThread().getName() + "开始执行第一步, Time:" + LocalDateTime.now());
            int millis = (int) (Math.random() * 5000);
            Thread.sleep(millis);
            System.out.println("线程: " + Thread.currentThread().getName() + "执行第一步完成, Time:" + LocalDateTime.now());
            this.countDownLatch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            executor.execute(new CountDownLatchRunnable(countDownLatch));
        }
        try {
            countDownLatch.await();
            System.out.println("第一步执行完成, 线程: " + Thread.currentThread().getName() + "开始执行第二步, Time:" + LocalDateTime.now());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 }

执行如下:
执行结果
可以看到,最后一个线程执行完成之后,开始第二步的操作。

构造函数

 public CountDownLatch(int count) {
     if (count < 0) throw new IllegalArgumentException("count < 0");
     this.sync = new Sync(count);
 }
 Sync(int count) {
 	 // 设置同步状态state的值
     setState(count);
 }

CountDownLatch初始化中,设置了同步器Sync的同步状态state,相当于一开始同步器就持有count个共享锁。
在主线程中,开启子线程后,会执行await()方法进入同步队列等待。

CountDownLatch进入等待

1、CountDownLatch-await进入等待状态入口

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

2、AQS-acquireSharedInterruptibly加入队列中自旋等待申请锁

 public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 如果线程中断,抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试申请共享锁
    if (tryAcquireShared(arg) < 0)
    	// 申请失败,加入等待队列进行等待
        doAcquireSharedInterruptibly(arg);
 }

3、CountDownLatch-tryAcquireShared尝试申请共享锁

 protected int tryAcquireShared(int acquires) {
    // 如果state的值为0, 则认为获取到了锁
     return (getState() == 0) ? 1 : -1;
 }

由于CountDownLatch设置了state的值,在子线程释放共享锁完成之前,这里获取到的state的值不会为0。

4、AQS-doAcquireSharedInterruptibly加入同步队列中等待

 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);
     }
 }

doAcquireSharedInterruptibly方法在Semaphore中解析过,这里不做赘述。在队列中自旋等待成功有效的第一个节点,然后执行tryAcquireShared方法获取共享锁,还会继续判断state是否为0,所有当所有子线程释放共享锁之后,主线程才会继续执行。

CountDownLatch释放共享锁

1、CountDownLatch-countDown释放共享锁入口

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

2、AQS-releaseShared释放共享锁

 public final boolean releaseShared(int arg) {
 	 // 尝试释放共享锁
     if (tryReleaseShared(arg)) {
     	 // 如果完全释放共享锁,唤醒同步队列中的节点
         doReleaseShared();
         return true;
     }
     return false;
 }

3、CountDownLatch-tryReleaseShared尝试释放共享锁

 protected boolean tryReleaseShared(int releases) {
 	 // 自旋
     for (;;) {
         // 如果state的值大于0, 则认为可以继续释放锁
         int c = getState();
         if (c == 0)
             return false;
         int nextc = c-1;
         // 通过CAS将state - 1
         if (compareAndSetState(c, nextc))
         	 // 返回state是否为0,为0表示完全释放共享锁
             return nextc == 0;
     }
 }

4、AQS-doReleaseShared唤醒同步队列中的节点

 private void doReleaseShared() {
     for (;;) {
         Node h = head;
         if (h != null && h != tail) {
             int ws = h.waitStatus;
             if (ws == Node.SIGNAL) {
                 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                     continue;            // loop to recheck cases
                 unparkSuccessor(h);
             }
             else if (ws == 0 &&
                      !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                 continue;          
         }
         if (h == head)      
             break;
     }
 }

doReleaseShared也在Semaphore中解析过,这里不做赘述。
对于文章中的demo来说,唤醒就是主线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值