打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例

概要

前面对"独占锁"和"共享锁"有了个大致的了解;本章,我们对CountDownLatch进行学习。和ReadWriteLock.ReadLock一样,CountDownLatch的本质也是一个"共享锁"。本章的内容包括:
CountDownLatch简介
CountDownLatch数据结构

CountDownLatch源码分析(基于JDK1.7.0_40)
CountDownLatch示例

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3533887.html

 

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。

 

CountDownLatch源码分析(基于JDK1.7.0_40)

CountDownLatch完整源码(基于JDK1.7.0_40)

 View Code

CountDownLatch是通过“共享锁”实现的。下面,我们分析CountDownLatch中3个核心函数: CountDownLatch(int count), await(), countDown()。

 

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队列末尾。关于Node和CLH在"Java多线程系列--“JUC锁”03之 公平锁(一)"已经详细介绍过,这里就不再重复说明了。
(02) node.predecessor()的作用是,获取上一个节点。如果上一节点是CLH队列的表头,则”尝试获取共享锁“。
(03) shouldParkAfterFailedAcquire()的作用和它的名称一样,如果在尝试获取锁失败之后,线程应该等待,则返回true;否则,返回false。
(04) 当shouldParkAfterFailedAcquire()返回ture时,则调用parkAndCheckInterrupt(),当前线程会进入等待状态,直到获取到共享锁才继续运行。
doAcquireSharedInterruptibly()中的shouldParkAfterFailedAcquire(), parkAndCheckInterrupt等函数在"Java多线程系列--“JUC锁”03之 公平锁(一)"中介绍过,这里也就不再详细说明了。

 

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)"之后,再继续运行。

 1 import java.util.concurrent.CountDownLatch; 2 import java.util.concurrent.CyclicBarrier; 3  4 public class CountDownLatchTest1 { 5  6     private static int LATCH_SIZE = 5; 7     private static CountDownLatch doneSignal; 8     public static void main(String[] args) { 9 10         try {11             doneSignal = new CountDownLatch(LATCH_SIZE);12 13             // 新建5个任务14             for(int i=0; i<LATCH_SIZE; i++)15                 new InnerThread().start();16 17             System.out.println("main await begin.");18             // "主线程"等待线程池中5个任务的完成19             doneSignal.await();20 21             System.out.println("main await finished.");22         } catch (InterruptedException e) {23             e.printStackTrace();24         }25     }26 27     static class InnerThread extends Thread{28         public void run() {29             try {30                 Thread.sleep(1000);31                 System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");32                 // 将CountDownLatch的数值减133                 doneSignal.countDown();34             } catch (InterruptedException e) {35                 e.printStackTrace();36             }37         }38     }39 }

运行结果

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

  


更多内容

1. Java多线程系列--“JUC锁”01之 框架 

2. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock 

3. Java多线程系列--“JUC锁”03之 公平锁(一) 

4. Java多线程系列--“JUC锁”04之 公平锁(二)

5. Java多线程系列--“JUC锁”05之 非公平锁

6. Java多线程系列--“JUC锁”06之 Condition条件

7. Java多线程系列--“JUC锁”07之 LockSupport 

8. Java多线程系列--“JUC锁”08之 共享锁和ReentrantReadWriteLock

9. Java多线程系列目录(共xx篇)

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Java并发系列(7)CountDownLatch源码分析
采用Thread.join()或CountDownLatch来实现线程间同步
快速掌握并发编程---CountDownLatch原理和实战
Java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)
并发模拟的四种方式
java多线程的工具
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服