欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java concurrency之CountDownLatch原理和示例_动力节点Java学院整理

程序员文章站 2023-12-16 16:42:10
countdownlatch简介 countdownlatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 ...

countdownlatch简介

countdownlatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 

countdownlatch和cyclicbarrier的区别

(01) countdownlatch的作用是允许1或n个线程等待其他线程完成执行;而cyclicbarrier则是允许n个线程相互等待。

(02) countdownlatch的计数器无法被重置;cyclicbarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

关于cyclicbarrier的原理,后面一章再来学习。

countdownlatch函数列表

countdownlatch(int count)

构造一个用给定计数初始化的 countdownlatch。

// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, timeunit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countdown()
// 返回当前计数。
long getcount()
// 返回标识此锁存器及其状态的字符串。
string tostring()

countdownlatch数据结构

countdownlatch的uml类图如下:

Java concurrency之CountDownLatch原理和示例_动力节点Java学院整理

countdownlatch的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是sync类型。sync是实例类,它继承于aqs。  

1. countdownlatch(int count)

public countdownlatch(int count) {
  if (count < 0) throw new illegalargumentexception("count < 0");
  this.sync = new sync(count);
}

说明:该函数是创建一个sync对象,而sync是继承于aqs类。sync构造函数如下:

sync(int count) {
  setstate(count);
} 

setstate()在aqs中实现,源码如下:

protected final void setstate(long newstate) {
  state = newstate;
}

说明:在aqs中,state是一个private volatile long类型的对象。对于countdownlatch而言,state表示的”锁计数器“。countdownlatch中的getcount()最终是调用aqs中的getstate(),返回的state对象,即”锁计数器“。 

2. await()

public void await() throws interruptedexception {
  sync.acquiresharedinterruptibly(1);
}

说明:该函数实际上是调用的aqs的acquiresharedinterruptibly(1);

aqs中的acquiresharedinterruptibly()的源码如下:

public final void acquiresharedinterruptibly(long arg)
    throws interruptedexception {
  if (thread.interrupted())
    throw new interruptedexception();
  if (tryacquireshared(arg) < 0)
    doacquiresharedinterruptibly(arg);
}

说明:acquiresharedinterruptibly()的作用是获取共享锁。

如果当前线程是中断状态,则抛出异常interruptedexception。否则,调用tryacquireshared(arg)尝试获取共享锁;尝试成功则返回,否则就调用doacquiresharedinterruptibly()。doacquiresharedinterruptibly()会使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。

tryacquireshared()在countdownlatch.java中被重写,它的源码如下:

protected int tryacquireshared(int acquires) {
  return (getstate() == 0) ? 1 : -1;
}

说明:tryacquireshared()的作用是尝试获取共享锁。

如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。

private void doacquiresharedinterruptibly(long arg)
  throws interruptedexception {
  // 创建"当前线程"的node节点,且node中记录的锁是"共享锁"类型;并将该节点添加到clh队列末尾。
  final node node = addwaiter(node.shared);
  boolean failed = true;
  try {
    for (;;) {
      // 获取上一个节点。
      // 如果上一节点是clh队列的表头,则"尝试获取共享锁"。
      final node p = node.predecessor();
      if (p == head) {
        long r = tryacquireshared(arg);
        if (r >= 0) {
          setheadandpropagate(node, r);
          p.next = null; // help gc
          failed = false;
          return;
        }
      }
      // (上一节点不是clh队列的表头) 当前线程一直等待,直到获取到共享锁。
      // 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
      if (shouldparkafterfailedacquire(p, node) &&
        parkandcheckinterrupt())
        throw new interruptedexception();
    }
  } finally {
    if (failed)
      cancelacquire(node);
  }
}

说明:

(01) addwaiter(node.shared)的作用是,创建”当前线程“的node节点,且node中记录的锁的类型是”共享锁“(node.shared);并将该节点添加到clh队列末尾。

(02) node.predecessor()的作用是,获取上一个节点。如果上一节点是clh队列的表头,则”尝试获取共享锁“。

(03) shouldparkafterfailedacquire()的作用和它的名称一样,如果在尝试获取锁失败之后,线程应该等待,则返回true;否则,返回false。

(04) 当shouldparkafterfailedacquire()返回ture时,则调用parkandcheckinterrupt(),当前线程会进入等待状态,直到获取到共享锁才继续运行。 

3. countdown()

public void countdown() {
  sync.releaseshared(1);
}

说明:该函数实际上调用releaseshared(1)释放共享锁。

releaseshared()在aqs中实现,源码如下:

public final boolean releaseshared(int arg) {
  if (tryreleaseshared(arg)) {
    doreleaseshared();
    return true;
  }
  return false;
}

说明:releaseshared()的目的是让当前线程释放它所持有的共享锁。

它首先会通过tryreleaseshared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doreleaseshared()去释放共享锁。

tryreleaseshared()在countdownlatch.java中被重写,源码如下:

protected boolean tryreleaseshared(int releases) {
  // decrement count; signal when transition to zero
  for (;;) {
    // 获取“锁计数器”的状态
    int c = getstate();
    if (c == 0)
      return false;
    // “锁计数器”-1
    int nextc = c-1;
    // 通过cas函数进行赋值。
    if (compareandsetstate(c, nextc))
      return nextc == 0;
  }
}

说明:tryreleaseshared()的作用是释放共享锁,将“锁计数器”的值-1。

总结:countdownlatch是通过“共享锁”实现的。在创建countdownlatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该countdownlatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该countdownlatch对象的countdown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countdown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!

以上,就是countdownlatch的实现原理。

countdownlatch的使用示例

下面通过countdownlatch实现:"主线程"等待"5个子线程"全部都完成"指定的工作(休眠1000ms)"之后,再继续运行。

 import java.util.concurrent.countdownlatch;
 import java.util.concurrent.cyclicbarrier;
 public class countdownlatchtest1 {
   private static int latch_size = 5;
   private static countdownlatch donesignal;
   public static void main(string[] args) {
     try {
       donesignal = new countdownlatch(latch_size);
       // 新建5个任务
       for(int i=0; i<latch_size; i++)
         new innerthread().start();
       system.out.println("main await begin.");
       // "主线程"等待线程池中5个任务的完成
       donesignal.await();
       system.out.println("main await finished.");
     } catch (interruptedexception e) {
       e.printstacktrace();
     }
   }
   static class innerthread extends thread{
     public void run() {
       try {
         thread.sleep(1000);
         system.out.println(thread.currentthread().getname() + " sleep 1000ms.");
         // 将countdownlatch的数值减1
         donesignal.countdown();
       } catch (interruptedexception e) {
         e.printstacktrace();
       }
     }
   }
 }

运行结果:

main await begin.
thread-0 sleep 1000ms.
thread-2 sleep 1000ms.
thread-1 sleep 1000ms.
thread-4 sleep 1000ms.
thread-3 sleep 1000ms.
main await finished.

结果说明:主线程通过donesignal.await()等待其它线程将donesignal递减至0。其它的5个innerthread线程,每一个都通过donesignal.countdown()将donesignal的值减1;当donesignal为0时,main被唤醒后继续执行。

上一篇:

下一篇: