发布于2021-05-29 21:33 阅读(1190) 评论(0) 点赞(4) 收藏(4)
CountDownLatch,在使用有道词典翻译出来的意思:
在百度上找了一圈,没有找到在计算机方面关于“闭锁”的官方解释,但是在一篇博客中是这样描述的:“闭锁是一种同步工具,可以延迟线程直到其达到其终止状态”。
在JDK中提供的工具类java.util.concurrent.CountDownLatch,就是对闭锁功能的一种实现。结合CountDownLatch的功能可以这样来描述闭锁:假设在一条道路上有一道门,行人(看作多线程)想穿过道路必须经过这道门。若给门上了锁之后,行人就必须阻塞在门前等待,直到锁被打开才能继续通行。给门上锁,锁的数量的任意的,可以上1把锁,可以上10把锁。若加了10把锁,那必须这10把锁全部都被打开了,行人才能通过这道门,否则所有的行人都需要阻塞等待。
关于上面的行人过马路的例子,来思考此功能的实现。
(1)如何定义给门加锁的个数
(2)如何实现门上有锁,行人就阻塞等待不能过马路
(3)如何实现打开门上的锁
(0)AbstractQueuedSynchronizer的实现类
CountDownLatch的功能实现也是借助了AbstractQueuedSynchronizer。在CountDownLatch内部定义了一个Sync内部内,主要的功能实现就是借助了这个Sync。
- private static final class Sync extends AbstractQueuedSynchronizer {
- private static final long serialVersionUID = 4982264981922014374L;
-
- Sync(int count) {
- setState(count);
- }
-
- int getCount() {
- return getState();
- }
-
- // 重写tryAcquireShared方法自定义实现获取共享锁的逻辑
- protected int tryAcquireShared(int acquires) {
- return (getState() == 0) ? 1 : -1;
- }
-
- // 重写tryReleaseShared自定义释放共享锁的逻辑
- protected boolean tryReleaseShared(int releases) {
- // 自旋
- for (;;) {
- int c = getState();
- // 当state==0时,表示没有锁,都没有锁了,就不会释放锁了,因此返回false
- if (c == 0)
- return false;
- // state的值减1
- int nextc = c-1;
- // CAS操作变更state值
- if (compareAndSetState(c, nextc))
- return nextc == 0;
- }
- }
- }
内部类Sync继承了AbstractQueuedSynchronizer,并重写了tryAcquireShared、tryReleaseShared。可见其是使用了AbstractQueuedSynchronizer中共享锁功能。(关于AbstractQueuedSynchronizer共享锁的介绍,请戳《JUC之AbstractQueuedSynchronizer共享模式》)为什么是共享锁呢?继续回到上面行人过马路的例子,在门前阻塞的行人会有多个,当门上的锁都被打开之后,是要所有的行人都可以继续通行的,而不是只允许一个行人继续通行,因此需要使用共享锁来实现。
(1)如何定义给门加锁的个数
在《JUC之ReentrantLock》中我们知道,ReentrantLock是使用了AbstractQueuedSynchronizer提供的state字段来记录锁的状态的。当然CountDownLatch也是使用的state字段来记录加锁的个数,当state=0时,表示没有加锁,此时所有的线程可以顺利通过继续执行逻辑;当state=1时,表示加了1把锁,此时所有的线程都阻塞住,直到这一把锁被释放,即state回到0值;同理state=10表示加了10把锁。
CountDownLatch提供了一个构造函数来初始化加锁的个数:
- // CountDownLatch也维护了一个变量sync
- private final Sync sync;
- // count指定加锁的个数
- public CountDownLatch(int count) {
- if (count < 0) throw new IllegalArgumentException("count < 0");
- this.sync = new Sync(count);
- }
new Sync,调用Sync构造器创建Sync:
- Sync(int count) {
- setState(count);
- }
setState是AbstractQueuedSynchronizer提供的方法,设置state的值:
- protected final void setState(int newState) {
- state = newState;
- }
Sync可以看作成门,state看作门上锁的个数。
(2)如何实现门上有锁,行人就阻塞等待不能过马路
使用了state来记录了锁的个数,当state=0时表示此时没有锁,线程不被阻塞住,继续执行。当state>0时,表示有锁存在,线程阻塞等待所有锁被释放。
CountDownLatch提供了await方法来实现无锁通行,有锁等待的逻辑。
- public void await() throws InterruptedException {
- sync.acquireSharedInterruptibly(1);
- }
在await中调用了Sync的acquireSharedInterruptibly方法,acquireSharedInterruptibly是继承自AbstractQueuedSynchronizer:
- public final void acquireSharedInterruptibly(int arg)
- throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- if (tryAcquireShared(arg) < 0)
- doAcquireSharedInterruptibly(arg);
- }
tryAcquireShared尝试去获取锁,是在Sync中重写该方法,去获取此时的state值,若state=0返回1,若state不为0返回-1。当state=0返回1时,上面的if (tryAcquireShared(arg) < 0)逻辑是不成立的,acquireSharedInterruptibly方法会执行完,很顺利没有阻塞。当state不为0返回-1,那么if条件语句就成立了,就会进入doAcquireSharedInterruptibly执行入队等待的逻辑(自旋,被唤醒 还在自旋代码中,接着继续执行逻辑,直到满足条件跳出自旋),直到满足条件获取锁成功。
- protected int tryAcquireShared(int acquires) {
- return (getState() == 0) ? 1 : -1;
- }
await方法会一直阻塞线程,直到满足条件获取锁成功返回。CountDownLatch还提供了超时等待的await方法,即在设置的时间达到之后,会停止阻塞,继续执行后续的逻辑,即使此时还有锁存在。此功能的实现依然借助于AbstractQueuedSynchronizer的tryAcquireSharedNanos。
- public boolean await(long timeout, TimeUnit unit)
- throws InterruptedException {
- return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
- }
(3)如何实现打开门上的锁
在线程调用了await时,如果此时门上有锁,这个线程会一直阻塞等待的,直到所有的锁被释放了。那锁如何被释放呢?
CountDownLatch提供countDown方法用于释放锁,并且调用一次该方法,只会释放一把锁。即将state的值减1。
- public void countDown() {
- sync.releaseShared(1);
- }
调用Sync的releaseShared,releaseShared也是继承自AbstractQueuedSynchronizer的方法:
- public final boolean releaseShared(int arg) {
- if (tryReleaseShared(arg)) {
- doReleaseShared();
- return true;
- }
- return false;
- }
tryReleaseShared在Sync中被重写了,此方法是只有释放了最后一把锁才会返回true(即将state的值从1变成了0),否则都是false。tryReleaseShared返回true会执行doReleaseShared方法,否则releaseShared直接返回了false。也就是当解开了门上的最后一把锁的时候,才会执行doReleaseShared方法。doReleaseShared会将在AbstractQueuedSynchronizer等待队列中的线程唤醒,继续执行在doAcquireSharedInterruptibly中自旋的代码逻辑。
- protected boolean tryReleaseShared(int releases) {
- for (;;) {
- int c = getState();
- if (c == 0)
- return false;
- int nextc = c-1;
- if (compareAndSetState(c, nextc))
- return nextc == 0;
- }
- }
补充:
(1)构造器的传参0
在使用CountDownLatch的构造器来创建对象时,设置初始化的state值为0也是合法的,不过这个效果和没上锁的效果是一样的,因为即使不显式给state设置,state是int类型的默认值也会为0。
CountDownLatch countDownLatch = new CountDownLatch(0);
(2)减法计数器
从上面描述的CountDownLatch的功能,可以将其理解成一个减法计数器,设置给定的state值,对state值执行减1操作,直到state恢复到0。
(3)无法重置
CountDownLatch作为闭锁功能,一旦在创建对象的时候设置了锁的个数,当锁全部被打开之后,是无法继续加锁的,CountDownLatch没有提供任何方法用于修改state的值,即CountDownLatch加的锁都是一次性的。(可以重置的多次加锁的功能见java.util.concurrent.CyclicBarrier)
使用例子:
- public static void main(String[] args) throws InterruptedException {
-
- CountDownLatch countDownLatch = new CountDownLatch(10);
-
- for (int i = 0; i < 10; i++) {
- new Thread(()->{
- try {
- Thread.sleep(2000);
- System.out.println("线程"+Thread.currentThread().getName()+"等待2秒");
- // state-1
- countDownLatch.countDown();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }).start();
- }
-
- System.out.println("主线程在等待其他线程执行完成...");
- countDownLatch.await(); // 在这里会阻塞等待
- System.out.println("其他线程执行完成,主线程执行到此");
- }
原文链接:https://blog.csdn.net/weixin_50518271/article/details/117301908
作者:我是不是很美
链接:http://www.javaheidong.com/blog/article/207437/acd7315b21a6578fc486/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!