CountDownLatch的工作原理
CountDownLatch在多线程并发编程中充当一个计时器的功能,并且维护一个count的变量,并且其操作都是原子操作,该类主要通过countDown()和await()两个方法实现功能的,首先通过建立CountDownLatch对象,并且传入参数即为count初始值。如果一个线程调用了await()方法,那么这个线程便进入阻塞状态,并进入阻塞队列。如果一个线程调用了countDown()方法,则会使count-1;当count的值为0时,这时候阻塞队列中调用await()方法的线程便会逐个被唤醒,从而进入后续的操作。比如下面的例子就是有两个操作,一个是读操作一个是写操作,现在规定必须进行完写操作才能进行读操作。所以当最开始调用读操作时,需要用await()方法使其阻塞,当写操作结束时,则需要使count等于0。因此count的初始值可以定为写操作的记录数,这样便可以使得进行完写操作,然后进行读操作。
CountDownLatch的内部类Sync继承了AQS(AbstractQueuedSynchronizer)抽象类,该内部类实现了 tryAcquireShared()方法 和 tryReleaseShared()方法,说明了底层使用了AQS的共享锁
内部维护了AQS的一个int类型的变量state,初始化将该变量设置为传入的int值,使用该并发工具主要使用其中的两个方法:await()和countDown()
调用await()方法的线程会进行一个自旋,当调用countDown()的线程数量达到state个时,调用await()方法的线程将继续运行。
CountDownLatch函数列表
// 构造一个用给定计数初始化的 CountDownLatch。
CountDownLatch(int count)
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
await方法
调用await() 方法,来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
该方法内部使用 AQS 的 acquireSharedInterruptibly(int arg) 方法
在内部类 Sync 中重写了 tryAcquireShared(int arg) 方法
通过AQS中的getState() 方法,获取同步状态,其值等于计数器的值
如果计数器值不等于 0,则会调用 doAcquireSharedInterruptibly(int arg) 方法,该方法为一个自旋方法会尝试一直去获取同步状态
countDown方法
调用countDown() 方法,来改变计数器数量
内部调用AQS的releaseShared() 方法
Sync重写了tryReleaseShared() 方法
释放锁,也就是操作计数器的过程,这里使用到了CAS(compareAndSetState)进行计数更新,若更新失败则进行自旋重试直到成功为止
getCount方法
调用AQS的getState方法获取计数
使用场景
某一线程在开始运行前等待n个线程执行完毕
例子:五个线程一起加载只用等待五个线程都执行完才能继续执行
package com.it.web;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class ThreadTest {
CountDownLatch countDownLatch=new CountDownLatch(5);
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
threadTest.m();
}
Random random=new Random();
public void m() throws InterruptedException {
String[] numbers= new String[5];
for (int i = 0; i <5 ; i++) {
int index=i;
new Thread(()->{
String name = Thread.currentThread().getName();
for (int j = 0; j <=100; j++) {
try {
Thread.sleep(random.nextInt(100));
numbers[index]=name+"%-"+j;
System.out.print("\r" + Arrays.toString(numbers));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t"+(i+1)).start();
countDownLatch.countDown();
}
countDownLatch.await();
}
}
总结
CountDownLatch的作用实际是提供了一种多线程通信的方式
AQS提供了多个线程并发且产生竞态条件时共享变量的安全读写方式,且丰富地提供了共享、排他两种处理竞态线程获取共享资源的方式
CountDownLatch利用AQS的state共享变量作为信号量,await方法会在state不等于0时进行阻塞,countDown方法会操作state共享变量改变这个信号量的值从而来影响整个CountDownLatch的运行
CountdownLatch是通过共享方式对锁进行操作,通过自旋方式进行阻塞等待,通过CAS对共享变量进行更新保证原子性