CountDownLatch

在复习了ReentrantLock可重入的独占锁后,我们来分析下使用了共享锁的CountDownLatch。

CountDownLatch使用示例

先看一个CountDownLatch的使用示例。

package com.wangcc.springbootexample.concurrency;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(4);
        for (int i = 0; i < latch.getCount(); i++) {
            new Thread(new MyThread(latch), "player" + i).start();
        }
        System.out.println("正在等待所有玩家准备好");
        latch.await();
        System.out.println("开始游戏");
    }

    private static class MyThread implements Runnable {
        private CountDownLatch latch;

        public MyThread(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                Random rand = new Random();
                int randomNum = rand.nextInt((3000 - 1000) + 1) + 1000;// 产生1000到3000之间的随机整数
                Thread.sleep(randomNum);
                System.out.println(Thread.currentThread().getName() + " 已经准备好了, 所使用的时间为 " + ((double) randomNum / 1000) + "s");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

程序输出如下:

正在等待所有玩家准备好
player2 已经准备好了, 所使用的时间为 1.687s
player1 已经准备好了, 所使用的时间为 1.992s
player0 已经准备好了, 所使用的时间为 2.095s
player3 已经准备好了, 所使用的时间为 2.453s
开始游戏

通过上述示例,我们能够知道CountDownLatch的用处。当一个线程的操作必须要等待其他几个线程执行完后才能继续执行的时候,就可以使用CountDownLatch来实现。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count个线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!

CountDownLatch源码分析

先看构造方法

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

CountDownLatchReentrantLock一样,有一个内部类,名为Sync,是AQS子类。CountDownLatch#Sync调用的是AQS对于共享锁的实现。在构造方法中,会将锁计数器的值count通过Sync的构造方法赋给AQS中的state属性。

 Sync(int count) {
            setState(count);
        }
     //AQS   
          protected final void setState(int newState) {
        state = newState;
    }

接下来看countDown()方法

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


直接调用AQS中的final方法releaseShared方法,该方法是尝试释放共享锁的方法,在分析AQS中分析过,是否成功释放共享锁,要看子类实现的tryReleaseShared()方法是否返回true,返回true则代表成功释放锁,就会去调用doReleaseShared()方法去唤醒阻塞线程。我们看下tryReleaseShared()的实现。

//CountDownLatch#Sync
    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;
                //CAS将state值置为nextc
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

实现非常简单,当state状态为0的时候,也就是当前共享锁已经释放了,那么就返回false。否则将state-1,CAS设置新的state值,去比较state-1的值是否等于0,如果等于0,那么就返回true,否则就返回false。通过这个方法,我们知道返回true的时候就是第count个线程调用countDown()的时候。

最后来看下await()方法

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

直接调用了AQS中的final方法acquireSharedInterruptibly()方法。这个方法的具体实现在AQS中已经分析过,我们知道当子类实现的tryAcquireShared()方法的返回值小于0的时候,会导致当前线程进入自旋去尝试获取锁。否则当返回值不小于0的时候,也就意味着当前线程已经获取到了共享锁。

tryAcquireShared(arg)

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

方法简单到令人发指,当state为0的时候就返回1,否则就返回-1。所以只有当有count个线程调用了countDown()方法后,才会获取到共享锁继续执行。AQS的设计就是这么好,我们只需要写很少的代码就能实现一个锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值