当前位置: 移动技术网 > IT编程>开发语言>Java > Java concurrency之CountDownLatch原理和示例_动力节点Java学院整理

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

2019年07月22日  | 移动技术网IT编程  | 我要评论

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类图如下:

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被唤醒后继续执行。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网