参考:用CountDownLatch提升请求处理速度,CountDownLatch的理解和使用
1.使用场景
看了网上的一些解释,这边直接摘取一下过来:
2.代码示例
代码使用的实例:
package huangzj.springmvc.controller;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
private CountDownLatch countDownLatch;
private int start = 10;
private int mid = 100;
private int end = 200;
private volatile int tmpRes1, tmpRes2;
private int add(int start, int end) {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
return sum;
}
private int sum(int a, int b) {
return a + b;
}
public void calculate() {
countDownLatch = new CountDownLatch(2);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 确保线程3先与1,2执行,由于countDownLatch计数不为0而阻塞
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " : 开始执行");
tmpRes1 = CountDownLatchDemo.this.add(start, mid);
System.out.println(Thread.currentThread().getName() +
" : calculate ans: " + tmpRes1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
}, "线程1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 确保线程3先与1,2执行,由于countDownLatch计数不为0而阻塞
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " : 开始执行");
tmpRes2 = CountDownLatchDemo.this.add(mid + 1, end);
System.out.println(Thread.currentThread().getName() +
" : calculate ans: " + tmpRes2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
}, "线程2");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " : 开始执行");
countDownLatch.await();
int ans = CountDownLatchDemo.this.sum(tmpRes1, tmpRes2);
System.out.println(Thread.currentThread().getName() +
" : calculate ans: " + ans);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程3");
thread3.start();
thread1.start();
thread2.start();
}
public static void main(String[] args) throws InterruptedException {
CountDownLatchDemo demo = new CountDownLatchDemo();
demo.calculate();
Thread.sleep(1000);
}
}
结果如下,在await之前的线程运行结束之后才会执行后面的线程:
3.代码阅读
1.构造方法
构造方法的入参表示,需要执行多少次线程之后,才进行释放,后面的线程才能运行
public CountDownLatch(int count) {
//这边的入参解释,count表示执行一定次数之后线程才能放过.
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
2.countDown
一个countDown表示一次共享锁的释放:
public void countDown() {
//释放一次锁.如果数量为0则表示等待的线程都执行完毕了
sync.releaseShared(1);
}
releaseShared是AQS提供的。
public final boolean releaseShared(int arg) {
//如果尝试释放锁成功
if (tryReleaseShared(arg)) {
//释放锁,修改参数
doReleaseShared();
return true;
}
return false;
}
我们主要来看一下tryReleaseShared这个方法是怎么释放的
如果状态本来就是0说明不需要释放,直接返回false,否则通过释放state成功返回true,则进入上面的doReleaseShared修改其他线程的waitState,告知锁释放.
/**
* 这边的实现就是死循环通过CAS的方式去释放state
*/
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;
}
}
}
3.await
await方法调用会造成当state不等于0其他线程进行等待,看这边的注释
public void await() throws InterruptedException {
//造成当前线程等待countDown计数降为0,除非线程本来就是中断状态
sync.acquireSharedInterruptibly(1);
}
这边如果线程是中断状态,再进行中断直接报错.
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程被中断报错
if (Thread.interrupted()){
throw new InterruptedException();
}
//尝试获取共享锁
if (tryAcquireShared(arg) < 0){
doAcquireSharedInterruptibly(arg);
}
}
tryAcquireShared这边判断是否获取锁成功的条件就是state是不是为0,这和我们上面说的一致,当countDown没有降到0的时候,其他线程是不能进行操作的。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
这边其实我们看到会往队列里面加一个共享节点,然后进入等待获取锁,直到获取成功为止。
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);
// help GC
p.next = null;
failed = false;
return;
}
}
//进入队列之后进行休眠,park,休眠之后如果判断线程状态是intercept,直接抛错
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
throw new InterruptedException();
}
}
} finally {
if (failed){
cancelAcquire(node);
}
}
}
4.我的理解
CountDownLatch之所以可以实现在await之前的线程执行结束才执行后面的线程,我的想法的线程是有在队列和不在队列的区分的,线程调用countDown方法表示该线程一定是在await之前去执行的。而调用await方法的线程一定是在countDown直到降为0才能进行运行的。
举个例子就是说:现在有线程A、B、C、D,AB运行结束之后产生结果,CD才能使用他们带来的结果进行计算。则在AB的运行结束之后需要释放计数器(-1操作)就是调用countDown方法。
而CD需要进行等待,在CD一开始就应该调用await方法,进入队列里面去等待,这样才能保证在AB运行的期间,CD不会去抢占线程获取到AB的运行权限。
我们用一张图来解释这个过程: