JUC —— CountDownLatch 的使用

什么是 CountDownLatch?

CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行

示例

在具体讲解 CountDownLatch 之前我们先来举个例子:

比如我们在高中上晚自习的时候,对吧

快下晚自习的时候班主任就会过来说:等全部同学离开教室的时候之后班长负责关一下门

班长:收到

然后我们的程序是这样子的

package juc;

import java.util.concurrent.CountDownLatch;

/**
 * @author Woo_home
 * @create by 2020/3/13
 */
public class CountDownLatchDemo {

	// 定义一个静态方法
    public static void closeDoor() {
    	// 模拟 8 个同学
        for (int i = 1; i <= 8; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "号同学离开教室");
            },String.valueOf(i)).start();
        }
        // main 线程代表的是我们的班长
        System.out.println(Thread.currentThread().getName() + "班长关门走人");
    }

    public static void main(String[] args) throws InterruptedException {
    	// 调用 closeDoor 静态方法
        closeDoor();
    }
}

这程序够简单吧?我知道你肯定会了,OK 执行一下
在这里插入图片描述
??? 班长怎么把三个同学锁在教室了?

这三个同学有点懵,怎么突然关灯锁门了?醉了

有些同学可能会问,会不会是关门的方式不对(哈哈)

OK 我们再执行几次试下:
在这里插入图片描述
。。。。。。

这说明我们的程序写得不对,按道理来说应该是全部同学都离开教室才关门的,对吧,所以我们的程序应该是这样写才对

public static void main(String[] args) throws InterruptedException {
	// 创建 CountDownLatch 并初始化大小
    CountDownLatch countDownLatch = new CountDownLatch(8);
    for (int i = 1; i <= 8; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "号同学离开教室");
            // 执行减一操作
            countDownLatch.countDown();
        },String.valueOf(i)).start();
    }
    // 唤醒
    countDownLatch.await();
    System.out.println(Thread.currentThread().getName() + "班长关门走人");
}

OK 运行一下
在这里插入图片描述
这个才是班长的正确关教室门姿势

CountDownLatch 的构造函数

我们 new 一个 CountDownLatch 对象的时候默认是使用下面这个构造函数

// 可以看到这个构造函数必须要传入一个 int 值
public CountDownLatch(int count) {
	// 如果传入的 count 值小于 0 的话会抛出 IllegalArgumentException 异常
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 否则就创建一个静态内部类 Sync 
    this.sync = new Sync(count);
}

构造器中的计数值(count)实际上就是闭锁需要等待的线程数量 。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值

静态内部类 Sync

CountDownLatch 的静态内部类 Sync 继承了 AQS 类(AbstractQueuedSynchronizer),会重写 AQS 类中的一些重要方法

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
        setState(count);
    }

    int getCount() {
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

CountDownLatch 常用的方法

CountDownLatch 主要有两个方法,当一个或多个线程调用 await() 方法时,这些线程会阻塞

  • 其它线程调用 countDown() 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
  • 当计数器的值变为 0 时,因 await() 方法阻塞的线程会被唤醒,继续执行

await() 方法

await() 方法是获取一个共享锁

acquireSharedInterruptibly(int arg) 方法是 AQS 中定义的,该方法是被 final 修饰的,无法被重写,但是子类可以重写里面调用的 tryAcquireShared(arg) 方法

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly(int arg)
public final void acquireSharedInterruptibly(int arg)
      throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
tryAcquireShared(int arg)
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
tryAcquireShared(int acquires)

当计数器的值为 0 的时候返回 1 : 表示获取锁成功,直接返回,线程可继续操作
当计数器的值不为 0 的时候返回 -1 : 表示获取锁失败,开始进入队列中排队等待,然后会继续按照 AQS 中的 acquireSharedInterruptibly 方法中的逻辑,执行doAcquireSharedInterruptibly(int arg)

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
doAcquireSharedInterruptibly(int arg)

AQS 中 doAcquireSharedInterruptibly(int arg) 实现如下 (该方法为一个自旋方法会尝试一直去获取同步状态)

private void doAcquireSharedInterruptibly(int arg)
  throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
parkAndCheckInterrupt()

调用 LockSupport 的 park() 方法,表示禁用当前线程

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

countDown() 方法

CountDownLatch 的 countDown() 方法如下:

方法递减锁存器的技术,如果计数到达零,则释放所有等待的线程(释放共享锁),也就是上述例子中的 main 线程

public void countDown() {
     sync.releaseShared(1);
}
releaseShared(int arg)

AQS 中的 releaseShared 方法实现如下:

同样地,releaseShared 方法是被 final 所修饰的,不能被重写,但是 CountDownLatch 的内部类 Sync 重写了 tryReleaseShared

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
tryReleaseShared(int releases)
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
    	// 获取锁状态
        int c = getState();
        if (c == 0)
        	// 如果计数器为 0 ,说明释放锁成功,直接返回
            return false;
        // 将计数器减一
        int nextc = c-1;
        // 使用 CAS 更新计算器的值
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

执行 AQS 中 releaseShared方法中的 doReleaseShared方法 去释放锁信息

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

总结

  • CountDownLatch 内部通过共享锁实现。在创建CountDownLatch实例时,需要传递一个int型的参数:count,该参数为计数器的初始值,也可以理解为该共享锁可以获取的总次数。

  • 当某个线程调用 await() 方法时,程序首先判断 count 的值是否为0,如果不为 0 的话则会一直等待直到 count 为 0

  • 当其他线程调用 countDown() 方法时,则执行释放共享锁状态,使 count 值 – 1

  • 当在创建 CountDownLatch 时初始化的 count 参数,必须要有 count 线程调用 countDown方法才会使计数器 count 等于 0,锁才会释放,前面等待的线程才会继续运行

注意:CountDownLatch 不能回滚重置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值