CountDownLatch怎么用?让你分分钟搞懂

本文详细解读了CountDownLatch在多线程中的应用,介绍了初始化、countDown和await方法的工作原理,展示了如何利用它实现线程按序执行。

CountDownLatch

最近对各种锁啊线程啊希望有更深入了解,什么乐观锁、悲观锁etc都去从理论实际理解了一些,然后这俩天做leetcode时做了个线程遍历的题。

这是原题链接线程按序遍历

题目中可以写个全局flag通过while(true)或者for(;;)来等待前面线程方法的完成,也可以使用信号量,然后我想起之前使用过的CountDownLatch,发现实现起来也很简单易读。

先贴个使用实例吧

class Foo {
	//获取俩个CountDownLatch实例
    CountDownLatch countDownLatch2 = new CountDownLatch(1);
    CountDownLatch countDownLatch3 = new CountDownLatch(1);
    public Foo() {}

    public void first(Runnable printFirst) throws InterruptedException {
        // printFirst.run() outputs "first". 
        printFirst.run();
        //first()运行完后将countDownLatch2减少
        countDownLatch2.countDown();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        countDownLatch2.await();
        // printFirst.run() outputs "second".
        printSecond.run();
        //first()运行完后将countDownLatch3减少
        countDownLatch3.countDown();
    }

    public void third(Runnable printThird) throws InterruptedException {
        countDownLatch3.await();
        // printThird.run() outputs "third". 
        printThird.run();
    }
}

在这里面是最简单的countDownLatch的使用,简单来理解就是使用constructor
CountDownLatch(int count)来获取CountDownLatch对象
这里的count是需要>=0的,否则会抛出exception

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

然后是将全局的sync初始化,Sync是一个静态类,它的初始化方法是调用了一个set方法

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

我们继续跟进,setState()方法仅仅是将全局可见变量state赋值,所以我们对整个CountDownLatch的初始化,实际上是对state进行赋值而已,就是这么一个简单的操作。
简而言之语句:

CountDownLatch countDownLatch = new CountDownLatch(1);

相当于初始化了一个state属性为1的CountDownLatch对象。
明白了初始化的作用之后,我们再来看await()方法和countDown()方法

秒一下countDown()方法

先来看countDown()方法,其本身的表层含义是非常明确的,其实就是将当前CountDownLatch的state属性进行减一操作,底层源码是这么进行的:

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

首先其实该方法是调用了Sync继承的AbstractQueuedSynchronizer(也就是通常所说的AQS)类的releaseShared(int arg)方法:

public final boolean releaseShared(int arg) {
	if (tryReleaseShared(arg)) {
		doReleaseShared();
		return true;
	}
	return false;
}

Sync类对其tryReleaseShared()进行了overridestryReleaseShared(int releases)

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;
	}
}

我们可以看到实际上源码是做了一个循环,我们可以注意一下,理论上while(true)for(;;)是一样的效果,但是在计算机底层编译实现的时候,for()的指令会比while()更少,所以效率更高,基于此,大多数时候底层源码都是使用的for(;;)而不是while(true)
这个函数其实也很好看懂,就是不断获取state值并判断其是否为0,如果不为0就将其减一,减一的时候进行的CAS操作。

CAS操作这里就不跟进了,简而言之就是:带入一个更改的地址,二是希望这个地址属性应该是什么值,三是需要修改成什么值,如果期望值不匹配就不进行修改。

概况来说,countDown()方法就是通过原子性的CAS操作将state属性减一。

最后来看一下await()方法

public void await() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}

源码中是调用了Sync继承自AQS的acquireSharedInterruptibly(int arg)方法,我们跟进,可以看到方法体里面首先是对线程是否中断进行了一个判断,如果线程没问题就去尝试查看state是否为0,如果为0,条件不通过,不为0条件通过

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
	if (Thread.interrupted())
		throw new InterruptedException();
	if (tryAcquireShared(arg) < 0)
		doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
	return (getState() == 0) ? 1 : -1;
}

不为0的情况下,其会进去调用doAcquireSharedInterruptibly(arg)方法

private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        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
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

该方法主要进行了一个自旋操作,尝试去挂起当前线程,这样就是是如果当前CountDownLatch类的state不为0,便会将当前线程挂起并等待state被更改为0后再跳出await()方法

怎么样,看完后是不是发现CountDownLatch其实很好用,初始化,countDown,await的代码逻辑都是非常清晰的,实现了一个等待其他线程将state减少世道当前线程跳出await状态的过程~

如果对你有帮助的话,建议点个赞噢QAQ

### CountDownLatch 的概念与使用方法 #### 什么是 CountDownLatch? `CountDownLatch` 是 Java 并发工具包 (`java.util.concurrent`) 中的一个同步辅助类,主要用于协调多个线程之间的执行顺序。它的核心功能是让一个或多个线程等待,直到其他线程完成一组特定的操作[^1]。 #### 工作机制 `CountDownLatch` 维护了一个内部计数器,在创建实例时初始化该计数值。每当调用 `countDown()` 方法时,计数器会减少 1;当计数器达到零时,所有因调用了 `await()` 而阻塞的线程将会被释放并继续执行[^2]。 #### 主要方法 以下是 `CountDownLatch` 类中的主要方法及其作用: - **`CountDownLatch(int count)`**: 构造函数,指定初始计数值。 - **`void await()`**: 当前线程进入等待状态,直至计数器变为 0 或超时。 - **`boolean await(long timeout, TimeUnit unit)`**: 让当前线程最多等待指定的时间,如果时间到达前计数器已降为 0,则提前结束等待。 - **`void countDown()`**: 将计数器减一。当计数器降至 0 后,所有正在等待的线程将继续运行[^4]。 #### 使用示例 下面是一个简单的例子,展示如何利用 `CountDownLatch` 来实现多线程间的同步: ```java import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { int numberOfThreads = 3; CountDownLatch latch = new CountDownLatch(numberOfThreads); Thread workerThread1 = new Worker(latch); Thread workerThread2 = new Worker(latch); Thread workerThread3 = new Worker(latch); workerThread1.start(); workerThread2.start(); workerThread3.start(); System.out.println("Waiting for all threads to complete..."); // Main thread waits until the countdown reaches zero. latch.await(); System.out.println("All tasks are completed."); } static class Worker extends Thread { private final CountDownLatch latch; public Worker(CountDownLatch latch) { this.latch = latch; } @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " is working"); Thread.sleep((long)(Math.random() * 1000)); // Simulate work being done System.out.println(Thread.currentThread().getName() + " has finished its task"); } catch (InterruptedException e) { e.printStackTrace(); } finally { latch.countDown(); // Decrease the counter when a task finishes } } } } ``` 在这个程序里,三个子线程分别模拟不同的工作任务,并通过调用 `latch.countDown()` 减少计数器值。主线程则调用 `latch.await()` 进入等待状态,只有当所有的子线程都完成了它们的任务并将计数器降到零之后,主线程才会恢复执行[^5]。 #### CountDownLatch vs Join() 相比传统的 `join()` 方法,`CountDownLatch` 提供了更多的灵活性和控制能力。它可以用来等待多个线程而不是单个线程,并且可以通过调整计数器来适应不同数量的工作单元。此外,`CountDownLatch` 可以在任何地方调用 `countDown()` 和 `await()`,使得线程间协作更加精细[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值