CountDownLatch分析

CountDownLatch分析

基于AQS的一个只能递减的计数器,且无法复用 ,所以一般放在业务方法里



简介

使用方式:可以在构造方法中设置任务数量,并行执行任务时,子线程执行完任务后将计数减一,等到计数为0时,代表所有的任务执行完毕,唤醒主线程执行后续的操作
基于AQS实现,主线程阻塞在AQS队列中,等待计数为0时唤醒主线程进行后续的业务

内部方法很少:
在这里插入图片描述


提示:以下是本篇文章正文内容,下面案例仅供参考

一、基本使用

public class CountDownLatchTest {

	// 创建一个线程池来执行任务
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3,
            3,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

	// 一般不用于类变量,因为无法复用,这里只是测试,一般放在业务里
    private static CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void task1() {
        System.out.println("执行任务1");
        try {
            // 执行1s
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务1执行结束");
        countDownLatch.countDown();
    }

    public static void task2() {
        System.out.println("执行任务2");
        try {
            // 执行1.5s
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务2执行结束");
        countDownLatch.countDown();
    }

    public static void task3() {
        System.out.println("执行任务3");
        try {
            // 执行2s
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务3执行结束");
        countDownLatch.countDown();
    }

    public static void main(String[] args) throws InterruptedException {
    	// 提交任务
        threadPoolExecutor.submit(CountDownLatchTest::task1);
        threadPoolExecutor.submit(CountDownLatchTest::task2);
        threadPoolExecutor.submit(CountDownLatchTest::task3);

        // 等待并行任务结束
        // countDownLatch.await();

        // 如果10s任务还没有处理完
        boolean await = countDownLatch.await(10000, TimeUnit.MINUTES);
        // 时间内没有处理完
        if (!await) {
            System.out.println("任务执行未结束,主线程执行额外的逻辑");
        } else {
            // 规定时间内任务处理完了
            System.out.println("并行任务执行结束,主线程执行后续逻辑");
        }
    }
}

二、原理分析

内部的计数器就是AQS的state

1.有参构造方法

代码如下(示例):

    public CountDownLatch(int count) {
        // 0虽然可以设置,但无意义
        if (count < 0) throw new IllegalArgumentException("count < 0");
        // 给state变量赋值
        this.sync = new Sync(count);
    }

2.await方法分析

代码如下(示例):

	// 阻塞主线程
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    // AQS实现
 	public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        // 线程被设置中断时,抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试获取资源,获取成功返回1,不进入阻塞逻辑中
        if (tryAcquireShared(arg) < 0)
        	// 未获取到资源,将主线程阻塞在AQS同步队列中(有子线程在执行任务)
            doAcquireSharedInterruptibly(arg);
    }

  	// countDownLatch实现
 	protected int tryAcquireShared(int acquires) {
 		// state如果为0说明资源没有被占用,返回1
        return (getState() == 0) ? 1 : -1;
	}
	// AQS实现,主线程入队阻塞
	private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // 将线程封装成node,追加到双向链表(同步队列)尾部
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
        	// 死循环,一直等子任务执行完,释放掉锁后,主线程抢占到锁为止
            for (;;) {
           	 	// 获取pre节点
                final Node p = node.predecessor();
                // head是伪节点,内部没有线程
                if (p == head) {
                	// 再尝试拿一次资源
                    int r = tryAcquireShared(arg);
                    // 拿到资源是唯一的出口
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 判断主线程是否允许挂起在队列中(WAITING)
                if (shouldParkAfterFailedAcquire(p, node) &&
                	// 执行LockSupport.park()线程挂起,如果线程被设置了中断标志位,抛出InterruptedException异常
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

3.分析countDown方法

将state减一,减成0时去AQS队列中唤醒主线程

    public void countDown() {
    	// 释放一次资源
        sync.releaseShared(1);
    }
    // AQS实现
   public final boolean releaseShared(int arg) {
   		// 如果资源被完全释放,进入if逻辑
        if (tryReleaseShared(arg)) {
        	// 唤醒AQS同步队列中阻塞的主线程
            doReleaseShared();
            return true;
        }
        return false;
    }
    // countDownLatch实现
    protected boolean tryReleaseShared(int releases) {
            // 死循环
            for (;;) {
                int c = getState();
                // 锁已经被释放完了
                if (c == 0)
                    return false;
                int nextc = c-1;
                // 	CAS修改state
                if (compareAndSetState(c, nextc))
                	// 如果完全释放,返回true
                    return nextc == 0;
            }
        }
        // AQS的原有逻辑
	private void doReleaseShared() {
	    // 死循环
	    for (;;) {
	        // 获取head
	        Node h = head;
	        // 如果head不为null && head不为tail,说明链表(AQS同步队列)里有值
	        if (h != null && h != tail) {
	            // 如果head的节点状态为-1
	            int ws = h.waitStatus;
	            if (ws == Node.SIGNAL) {
	                // 将head节点状态修改为0
	                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
	                    // 修改失败再来一次
	                    continue;            // loop to recheck cases
	                // 修改成功,唤醒head的下一节点,如果next有问题,继续向下寻找可以唤醒的节点
	                unparkSuccessor(h);
	            }
	            // head节点状态不为-1
	            else if (ws == 0 &&
	                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
	                continue;                // loop on failed CAS
	        }
	        if (h == head)                   // loop if head changed
	            break;
	    }
	}

总结

使用AQS中内置state作为资源数,并发执行任务时,每个线程执行完任务后都执行countDown()方法递减资源数,减成0时会执行doReleaseShared方法去AQS同步队列中唤醒被阻塞的主线程
主线程也可以不等待子线程执行任务完毕,使用带有阻塞时间的await方法,时间到后自动唤醒,返回值是布尔,可以代表任务是否处理完了
countDownLatch使用完后,无法再次使用,需要重新new

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值