java多线程并发之CountDownLatch

CountDownLatch : 主线程同时启动所有子线程,等待所有子线程都执行完毕,才重新执行主线程;其内部的计数器继承了AQS,AQS内部维持了一个volatile变量 state,用来表示同步状态,

(1) CountDownLatch(int count) 初始化计数器:当执行CountDownLatch downCountDownLatch = new CountDownLatch(5)时,已初始化一个基于AQS的同步队列,并把传进来的计数器赋给AQS队列的stat【setState(count)】,stat的值也表示一个针对CountdownLatch当前剩余的计数次数;

// 继承了AQS
 private static final class Sync extends AbstractQueuedSynchronizer {...}
/**
 * 创建一个值为count的计数器 
 */ 
public CountDownLatch(int count) {
	if (count < 0) throw new IllegalArgumentException("count < 0");
	// Sync 是CountDownLatch的一个内部类
	this.sync = new Sync(count);
}

Sync(int count) {
    // 继承自AQS
    setState(count);
}

(2)void await():阻塞当前进程

当调用await的时候,当前线程会被阻塞,同时加入到AQS阻塞队列,实际上是调用了AQS的acquireSharedInterruptibly方法,当线程调用了await方法时,该线程将被阻塞,直到下面两种情况之一:
(a)当所有线程都调用了countDown方法,也就是计数值为0时,
(b)当前线程的interrupted()方法被其他线程调用,就会抛出InterruptedException异常

/**
 * 阻塞当前进程,将当前进程加入阻塞队列
 */
public void await() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}

//AQS获取共享资源时可被中断的方法,
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //如果线程被中断则抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //查看当前计数器值是否为0,
    if (tryAcquireShared(arg) < 0)
		// 调用AQS的doAcquireSharedInterruptibly方法让当前线程阻塞
        // 实际上会构建阻塞队列的双向链表,挂起当前线程
        doAcquireSharedInterruptibly(arg);
}

//sync类实现的AQS的接口
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
线程获取资源时可以被中断,并且获取的资源是共享资源.

(3)boolean await(long timeout, TimeUnit unit)

和public void await()区别仅在于当线程阻塞时间超过timeout时,会返回false,计数器为0时,则返回true,发生中断会抛出异常,而void await()方法没有返回值

(4)void countDown():计数器减1,当计数值为0时,则释放所有因调用await方法阻塞的线程,否则什么都不做

public void countDown() {
    //委托sync调用AQS的方法
    sync.releaseShared(1);
}

//AQS的方法
public final boolean releaseShared(int arg) {
    //调用sync实现的tryReleaseShared
    if (tryReleaseShared(arg)) {
        //AQS的释放资源方法
        doReleaseShared();
        return true;
    }
    return false;
}
// Syc 实现的tryReleaseShared
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;
        // 调用AQS的cas
		if (compareAndSetState(c, nextc))
			return nextc == 0;
	}
}

调用countDown()方法时是原子性递减AQS的state状态值,CountDownLatch中的Sync会优先尝试修改state的值,来获取同步状态。例如,如果某个线程成功的将state的值从0修改为1,表示成功的获取了同步状态。 这个修改的过程是通过CAS完成的,所以可以保证线程安全。

反之,如果修改state失败,则会将当前线程加入到AQS的队列中,并阻塞线程。

当CountDownLatch因异常导致计数值不能正常递减,最终到达0时,相应实例上的线程就会一直处于WAITING状态,有两种方式可避免出现这种现象:(a)调用countDown()方法时,尽量放在finally代码块内;(b)使用CountDownLatch.await(long,TimeUnit)方法,超时之后等待线程会自动唤醒

(5)long getCount():当前的计数器值

/**
 * 返回当前计数器的值
 */
public long getCount() {
    return sync.getCount();
}
// 其内部还是调用了 AQS 的getState来获取state值
int getCount() {
    return getState();
}

CountDownLatch 是线程安全的

CountDownLatch和join的区别

使用join方法必须等待多个线程执行完毕之后才能解除阻塞状态,而CountDownLatch相对灵活,可以通过调用countDown方法来减少计数,唤醒被阻塞的线程

参考的实例

package com.example.lettcode.concurrent;

import cn.hutool.core.util.RandomUtil;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @Class CountDownLatchDemo
 * @Description TODO
 * @Author
 * @Date 2021/4/8
 **/
public class CountDownLatchDemo {
    public static void main(String[] args) {
        int startCount = 1;
        CountDownLatch startCountDownLatch = new CountDownLatch(startCount);

        int downCount = 5;
        CountDownLatch downCountDownLatch = new CountDownLatch(downCount);

        for (int i = 0; i < downCount; i++) {
            new Thread(new WorkerRunnable(startCountDownLatch, downCountDownLatch), "线程:" + i).start();
        }
        try {
            // 休眠1s ,保证所有的线程都已经调用了await方法,进入阻塞状态
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("所有线程都已启动!!!");
        // 唤醒所有阻塞的子线程
        startCountDownLatch.countDown();
        System.out.println("等待所有线程执行结束!!!");
        try {
            // 此处是为了让主线程阻塞,等待所有子线程执行完毕
            downCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("所有任务都已执行结束!!!");
    }

    static class WorkerRunnable implements Runnable {

        private CountDownLatch startCountDownLatch;
        private CountDownLatch downCountDownLatch;

        public WorkerRunnable(CountDownLatch startCountDownLatch, CountDownLatch downCountDownLatch) {
            this.startCountDownLatch = startCountDownLatch;
            this.downCountDownLatch = downCountDownLatch;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 已经准备好。。。");
            try {
                // 等待所有子线程都准备完毕再开始
                startCountDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            childThreadWork();
            try {
                downCountDownLatch.countDown();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void childThreadWork() {
            System.out.println(Thread.currentThread().getName() + "开始执行");
            try {
                TimeUnit.SECONDS.sleep(RandomUtil.randomInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

posted @ 2021-04-27 15:30  枫叶艾辰  阅读(344)  评论(0编辑  收藏  举报