本站消息

站长简介/公众号


站长简介:逗比程序员,理工宅男,前每日优鲜python全栈开发工程师,利用周末时间开发出本站,欢迎关注我的微信公众号:幽默盒子,一个专注于搞笑,分享快乐的公众号

  价值13000svip视频教程,java大神匠心打造,零基础java开发工程师视频教程全套,基础+进阶+项目实战,包含课件和源码

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2021-05(14)

2021-06(54)

2021-07(10)

2021-08(60)

2021-09(21)

JUC之CountDownLatch

发布于2021-05-29 21:33     阅读(691)     评论(0)     点赞(4)     收藏(4)


CountDownLatch,在使用有道词典翻译出来的意思:

在百度上找了一圈,没有找到在计算机方面关于“闭锁”的官方解释,但是在一篇博客中是这样描述的:“闭锁是一种同步工具,可以延迟线程直到其达到其终止状态”。


在JDK中提供的工具类java.util.concurrent.CountDownLatch,就是对闭锁功能的一种实现。结合CountDownLatch的功能可以这样来描述闭锁:假设在一条道路上有一道门,行人(看作多线程)想穿过道路必须经过这道门。若给门上了锁之后,行人就必须阻塞在门前等待,直到锁被打开才能继续通行。给门上锁,锁的数量的任意的,可以上1把锁,可以上10把锁。若加了10把锁,那必须这10把锁全部都被打开了,行人才能通过这道门,否则所有的行人都需要阻塞等待。

关于上面的行人过马路的例子,来思考此功能的实现。

(1)如何定义给门加锁的个数

(2)如何实现门上有锁,行人就阻塞等待不能过马路

(3)如何实现打开门上的锁


(0)AbstractQueuedSynchronizer的实现类

CountDownLatch的功能实现也是借助了AbstractQueuedSynchronizer。在CountDownLatch内部定义了一个Sync内部内,主要的功能实现就是借助了这个Sync。

  1. private static final class Sync extends AbstractQueuedSynchronizer {
  2. private static final long serialVersionUID = 4982264981922014374L;
  3. Sync(int count) {
  4. setState(count);
  5. }
  6. int getCount() {
  7. return getState();
  8. }
  9. // 重写tryAcquireShared方法自定义实现获取共享锁的逻辑
  10. protected int tryAcquireShared(int acquires) {
  11. return (getState() == 0) ? 1 : -1;
  12. }
  13. // 重写tryReleaseShared自定义释放共享锁的逻辑
  14. protected boolean tryReleaseShared(int releases) {
  15. // 自旋
  16. for (;;) {
  17. int c = getState();
  18. // 当state==0时,表示没有锁,都没有锁了,就不会释放锁了,因此返回false
  19. if (c == 0)
  20. return false;
  21. // state的值减1
  22. int nextc = c-1;
  23. // CAS操作变更state值
  24. if (compareAndSetState(c, nextc))
  25. return nextc == 0;
  26. }
  27. }
  28. }

内部类Sync继承了AbstractQueuedSynchronizer,并重写了tryAcquireSharedtryReleaseShared。可见其是使用了AbstractQueuedSynchronizer中共享锁功能。(关于AbstractQueuedSynchronizer共享锁的介绍,请戳《JUC之AbstractQueuedSynchronizer共享模式)为什么是共享锁呢?继续回到上面行人过马路的例子,在门前阻塞的行人会有多个,当门上的锁都被打开之后,是要所有的行人都可以继续通行的,而不是只允许一个行人继续通行,因此需要使用共享锁来实现。

 

(1)如何定义给门加锁的个数

在《JUC之ReentrantLock》中我们知道,ReentrantLock是使用了AbstractQueuedSynchronizer提供的state字段来记录锁的状态的。当然CountDownLatch也是使用的state字段来记录加锁的个数,当state=0时,表示没有加锁,此时所有的线程可以顺利通过继续执行逻辑;当state=1时,表示加了1把锁,此时所有的线程都阻塞住,直到这一把锁被释放,即state回到0值;同理state=10表示加了10把锁。

CountDownLatch提供了一个构造函数来初始化加锁的个数:

  1. // CountDownLatch也维护了一个变量sync
  2. private final Sync sync;
  1. // count指定加锁的个数
  2. public CountDownLatch(int count) {
  3. if (count < 0) throw new IllegalArgumentException("count < 0");
  4. this.sync = new Sync(count);
  5. }

new Sync,调用Sync构造器创建Sync:

  1. Sync(int count) {
  2. setState(count);
  3. }

setState是AbstractQueuedSynchronizer提供的方法,设置state的值:

  1. protected final void setState(int newState) {
  2. state = newState;
  3. }

Sync可以看作成门,state看作门上锁的个数。

 

(2)如何实现门上有锁,行人就阻塞等待不能过马路

使用了state来记录了锁的个数,当state=0时表示此时没有锁,线程不被阻塞住,继续执行。当state>0时,表示有锁存在,线程阻塞等待所有锁被释放。

  • await

CountDownLatch提供了await方法来实现无锁通行,有锁等待的逻辑。

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

在await中调用了Sync的acquireSharedInterruptibly方法,acquireSharedInterruptibly是继承自AbstractQueuedSynchronizer:

  1. public final void acquireSharedInterruptibly(int arg)
  2. throws InterruptedException {
  3. if (Thread.interrupted())
  4. throw new InterruptedException();
  5. if (tryAcquireShared(arg) < 0)
  6. doAcquireSharedInterruptibly(arg);
  7. }

tryAcquireShared尝试去获取锁,是在Sync中重写该方法,去获取此时的state值,若state=0返回1,若state不为0返回-1。当state=0返回1时,上面的if (tryAcquireShared(arg) < 0)逻辑是不成立的,acquireSharedInterruptibly方法会执行完,很顺利没有阻塞。当state不为0返回-1,那么if条件语句就成立了,就会进入doAcquireSharedInterruptibly执行入队等待的逻辑(自旋,被唤醒 还在自旋代码中,接着继续执行逻辑,直到满足条件跳出自旋),直到满足条件获取锁成功。

  1. protected int tryAcquireShared(int acquires) {
  2. return (getState() == 0) ? 1 : -1;
  3. }
  • await(long timeout, TimeUnit unit)

await方法会一直阻塞线程,直到满足条件获取锁成功返回。CountDownLatch还提供了超时等待的await方法,即在设置的时间达到之后,会停止阻塞,继续执行后续的逻辑,即使此时还有锁存在。此功能的实现依然借助于AbstractQueuedSynchronizer的tryAcquireSharedNanos

  1. public boolean await(long timeout, TimeUnit unit)
  2. throws InterruptedException {
  3. return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
  4. }

 

(3)如何实现打开门上的锁

在线程调用了await时,如果此时门上有锁,这个线程会一直阻塞等待的,直到所有的锁被释放了。那锁如何被释放呢?

  • countDown

CountDownLatch提供countDown方法用于释放锁,并且调用一次该方法,只会释放一把锁。即将state的值减1。

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

调用Sync的releaseShared,releaseShared也是继承自AbstractQueuedSynchronizer的方法:

  1. public final boolean releaseShared(int arg) {
  2. if (tryReleaseShared(arg)) {
  3. doReleaseShared();
  4. return true;
  5. }
  6. return false;
  7. }

tryReleaseShared在Sync中被重写了,此方法是只有释放了最后一把锁才会返回true(即将state的值从1变成了0),否则都是false。tryReleaseShared返回true会执行doReleaseShared方法,否则releaseShared直接返回了false。也就是当解开了门上的最后一把锁的时候,才会执行doReleaseShared方法。doReleaseShared会将在AbstractQueuedSynchronizer等待队列中的线程唤醒,继续执行在doAcquireSharedInterruptibly中自旋的代码逻辑。

  1. protected boolean tryReleaseShared(int releases) {
  2. for (;;) {
  3. int c = getState();
  4. if (c == 0)
  5. return false;
  6. int nextc = c-1;
  7. if (compareAndSetState(c, nextc))
  8. return nextc == 0;
  9. }
  10. }

 

补充:

(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


使用例子:

  1. public static void main(String[] args) throws InterruptedException {
  2. CountDownLatch countDownLatch = new CountDownLatch(10);
  3. for (int i = 0; i < 10; i++) {
  4. new Thread(()->{
  5. try {
  6. Thread.sleep(2000);
  7. System.out.println("线程"+Thread.currentThread().getName()+"等待2秒");
  8. // state-1
  9. countDownLatch.countDown();
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. }).start();
  14. }
  15. System.out.println("主线程在等待其他线程执行完成...");
  16. countDownLatch.await(); // 在这里会阻塞等待
  17. System.out.println("其他线程执行完成,主线程执行到此");
  18. }

原文链接:https://blog.csdn.net/weixin_50518271/article/details/117301908



所属网站分类: 技术文章 > 博客

作者:我是不是很美

链接:http://www.javaheidong.com/blog/article/207437/acd7315b21a6578fc486/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

4 0
收藏该文
已收藏

评论内容:(最多支持255个字符)