Java concurrency之CountDownLatch原理和示例_动力节点Java学院整理
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被唤醒后继续执行。
推荐阅读
-
Java concurrency集合之CopyOnWriteArraySet_动力节点Java学院整理
-
Java concurrency之CountDownLatch原理和示例_动力节点Java学院整理
-
Java concurrency线程池之线程池原理(二)_动力节点Java学院整理
-
Java concurrency线程池之线程池原理(一)_动力节点Java学院整理
-
Java class文件格式之常量池_动力节点Java学院整理
-
Java class文件格式之属性_动力节点Java学院整理
-
Java class文件格式之访问标志信息_动力节点Java学院整理
-
Java class文件格式之属性详解_动力节点java学院整理
-
Java class文件格式之数据类型(二)_动力节点Java学院整理
-
Java class文件格式之特殊字符串_动力节点Java学院整理