JUC锁——CountDownLatch

一、什么是CountDownLatch

CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他2个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

方法列表:

CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。

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

二、CountDownLatch数据结构

CountDownLatch通过共享锁实现。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于AQS。

三、源码分析

3.1 CountDownLatch(int count)

该方法主要就是创建一个Sync对象,Sync是一个内部类,继承于AQS:

创建Sync对象时,构造函数中的setState方法在AQS内部实现:

在AQS中,state是一个private volatile long类型的对象。

对于CountDownLatch而言,state表示的”锁计数器“。CountDownLatch中的getCount()最终是调用AQS中的getState(),返回的state对象,即”锁计数器“。

3.2 await()

该方法实际上是调用的AQS的acquireSharedInterruptibly(1);

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

tryAcquireShared()在CountDownLatch.java中被重写

tryAcquireShared()的作用是尝试获取共享锁。如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。

doAcquireSharedInterruptibly(需要补充)

3.3 countDown()

该函数实际上调用releaseShared(1)释放共享锁。

releaseShared在AQS中的实现:

releaseShared()的目的是让当前线程释放它所持有的共享锁。它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功(返回false),则直接返回;尝试失败(返回true,最后一个线程执行counrDown),则通过doReleaseShared()去释放共享锁,“主线程”就可以继续执行了。

tryReleaseShared()在CountDownLatch.java中被重写:

如上代码可以看到首先获取当前状态值(计数器值),如果当前状态值为 0 则直接返回 false ,则countDown()方法直接返回;否则执行代码使CAS设置计数器减一,CAS失败则循环重试,否则如果当前计数器为 0 则返回 true 。返回 true 后,说明当前线程是最后一个调用countDown()方法的线程,那么该线程除了让计数器减一外,还需要唤醒调用CountDownLatch的await 方法而被阻塞的线程。之所以使用第一个if进行判断, 是为了防止计数器值为 0 后,其他线程又调用了countDown方法,状态值就会变成负数。

四、CountDownLatch的使用示例

主线程等待两个子线程执行完毕后再执行:

package com.juc.countdownlatch;

import java.util.concurrent.CountDownLatch;

/**
 * @Author: 98050
 * @Time: 2018-12-20 20:50
 * @Feature: CountDownLatch的使用
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(2);

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+",子线程开始执行");
                /**
                 * 计数器的值减一
                 */
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName()+",子线程执行结束");
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+",子线程开始执行");
                /**
                 * 计数器的值减一
                 */
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName()+",子线程执行结束");
            }
        }).start();
        /**
         * 计数器值为0,恢复任务继续执行
         */
        countDownLatch.await();

        System.out.println("两个子线程执行完毕,主线程继续执行");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
        }
    }
}

五、应用场景

1、其它的一些线程需要某个线程做准备工作。例如:数据库的连接等。

2、某个线程需要等待一些线程工作完之后清理资源。断开数据库的连接等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值