专栏首页Liusy01CountDownLatch解析

CountDownLatch解析

CountDownLatch是JUC包下的一个工具类,允许一个或多个线程等待,直到其他线程中执行的一个放行操作完后,等待线程才会继续往下执行的同步辅助。

可用于一个或多个线程中等待其他线程完成某项操作后再运行的场景。

首先看一下其用法: 1、先创建一个CountDownLatch的实例

参数是计数器(可以设置大于1的),也就是必须要设定的线程执行完后等待线程才会往下执行。

设置一个共享变量sharedNum,初始值为0 
int sharedNum = 0;
CountDownLatch countDownLatch = new CountDownLatch(1);

2、创建多个线程,此处设置两个线程,分别是A和B,下面代码的意思是A线程必须等待B线程执行完后,A线程才会往下执行

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            1、调用await方法,使线程阻塞
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"获取sharedNum的值为"+sharedNum);
    }
},"A").start();

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sharedNum = 2;
        System.out.println(Thread.currentThread().getName()+"将sharedNum设置为2");
        2、调用countDown方法,将技术器减1
        countDownLatch.countDown();
    }
},"B").start();

上述代码执行结果始终都是:

接下来看一下其源代码:

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

底层使用Sync类,Sync是AQS的子类,AQS里面维护了锁的状态state字段和线程等待队列。AQS是并发包里面很重要的一个抽象类。

class Sync extends AbstractQueuedSynchronizer {
       Sync(int count) {
            setState(count);
        }
}
void setState(int newState) {
    state = newState;
}

上述可以看到,设定的计数值是直接设置锁状态的。

设置了锁状态之后,等待线程需要调用await方法线程才会进行等待。

await() throws InterruptedException {
    1、以共享模式获取对象锁,如果线程中断则终止
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException 
     --线程中断抛出异常终止
    if (Thread.interrupted())
        throw new InterruptedException();
    2、尝试获取锁,返回1可以获取
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
} 
protected int tryAcquireShared(int acquires) {
    3、只有当锁状态为0的时候才可获取锁,即返回1   
    return (getState() == 0) ? 1 : -1;
}

而一开始我们初始化CountDownLatch的时候并不是为0,而是为1,所以调用await的线程并不会获取到锁。

此时会执行doAcquireSharedInterruptibly方法,当前线程进入AQS维护的线程等待队列中,死循环的去获取共享锁(可以同时被多个线程获取)。

private void doAcquireSharedInterruptibly(int arg)
  throws InterruptedException 
  4、将当前线程加入等待队列中 
  final Node node = addWaiter(Node.SHARED);
  boolean failed = true;
  try {
      for (;;) {
          final Node p = node.predecessor();
          if (p == head) {
              5、重试获取锁,只有当锁状态为0的时候才可获取,
              获取到之后r是1
              int r = tryAcquireShared(arg);
              if (r >= 0) {
                  6、获取之后将当前线程设置为线程头结点,
                  并去唤醒下一个节点上的线程 
                  setHeadAndPropagate(node, r);
                  p.next = null; // help GC
                  failed = false;
                  return;
              }
          }
          7、获取失败后会检查上一个节点的状态,看是否需要阻塞 
          if (shouldParkAfterFailedAcquire(p, node) &&
              parkAndCheckInterrupt())
              throw new InterruptedException();
      }
  } finally {
      if (failed)
          cancelAcquire(node);
  }
}

第6步获取锁之后将当前线程设置为头结点,并尝试去唤醒下一个线程获取共享锁

 private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        --唤醒下一个等待的线程 
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

上述就是等待线程去获取锁的流程,当锁状态不为0的时候,调用await的线程会一直阻塞,直至锁状态为0,那么什么时候锁的状态会为0呢?

这就是countDown方法的用处了,没调用一次countDown方法,锁的状态都会进行减低1,直至锁状态为0

public void countDown() {
    1、释放锁
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    2、尝试释放共享锁,也就是将锁状态减1
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
protected boolean tryReleaseShared(int releases) {
    3、锁状态递减1,直至为0返回true,否则返回false
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

当锁状态递减为0的时候,调用await的等待线程就可以获取到锁,此时等待线程就可以被唤醒继续往下执行了。

上述就是CountDownLatch的用法以及源码解析。

本文分享自微信公众号 - Liusy01(Liusy_01),作者:Liusy01

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CyclicBarrier用法及解析

    上一篇聊了一下Semaphore信号灯的用法及源码,这一篇来聊一下CyclicBarrier的用法及解析。

    Liusy
  • 开篇!JAVA线程池

    由于工作中基本都是CRUD操作,对线程池不常用,所以一直没去具体了解过其底层原理,但是在工作、健身之余一直有一颗躁动的心,想在技术上浸淫的更深入一点...

    Liusy
  • 锁:Sychronized、Lock

    锁是用来在多线程并发阶段保障数据同步的重要手段,防止出现脏数据,加锁代码在某个时间点只能由一个线程运行,其他线程等待。

    Liusy
  • 成为高级程序员不得不了解的并发

    到目前为止,你学到的都是顺序编程,顺序编程的概念就是某一时刻只有一个任务在执行,顺序编程固然能够解决很多问题,但是对于某种任务,如果能够并发的执行程序中重要的部...

    用户4143945
  • 成为高级程序员不得不了解的并发

    到目前为止,你学到的都是顺序编程,顺序编程的概念就是某一时刻只有一个任务在执行,顺序编程固然能够解决很多问题,但是对于某种任务,如果能够并发的执行程序中重要的部...

    cxuan
  • Java并发编程:线程控制

    在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期。这篇文章将深入讲解Java如何对线程进行状态控制,比如...

    陈树义
  • Android线程池的详细说明(二)

    Oceanlong
  • 学习Java基础知识,打通面试关~十五线程池学习

    用户2196435
  • 详解Hystrix资源隔离

    在货船中,为了防止漏水和火灾的扩散,一般会将货仓进行分割,避免了一个货仓出事导致整艘船沉没的悲剧。同样的,在Hystrix中,也采用了这样的舱壁模式,将系统中的...

    aoho求索
  • 【小家Java】一次Java线程池误用(newFixedThreadPool)引发的线上血案和总结

    自从最近的某年某月某天起,线上服务开始变得不那么稳定(软病)。在高峰期,时常有几台机器的内存持续飙升,并且无法回收,导致服务不可用。

    YourBatman

扫码关注云+社区

领取腾讯云代金券