JMM——AQS之CountDownLatch源码解读

CountDownLatch

1. CountDownLatch是什么?

一般被称作"计数器",作用大致就是数量达到了某个点之后计数结束,才能继续往下走。可以用作流程控制之类的作用,大流程分成多个子流程,然后大流程在子流程全部结束之前不动(子流程最好是相互独立的,除非能很好的控制两个流程的关联关系),子流程全部结束后大流程开始操作。

2.CountDownLatch的用法

// 创建countDownLatch对象。
CountDownLatch countDownLatch = new CountDownLatch(5);
// 释放锁,将锁的数量-1,在子线程中设置。
countDownLatch.countDown();
// 获取锁,在主线程设置,进行阻塞。
 countDownLatch.await();

使用Demo

public class CountDownLatchDemo {
    private static final CountDownLatch countDownLatch = new CountDownLatch(5);
    public static void main(String[] args) {

        for (int i = 0; i < 5;i++){
            new Thread(new CountDownLatchRunnable()).start();
        }

        try {
            Thread.sleep(1000);
            // 主线程会阻塞到此,直到所有的子线程都执行完
            countDownLatch.await();
            System.out.println("____________");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class CountDownLatchRunnable implements Runnable{

        @Override
        public void run() {
            System.out.println("前"+ Thread.currentThread().getName());
            countDownLatch.countDown();
            System.out.println("后"+ Thread.currentThread().getName());
        }
    }
}

3.CountDownLatch的使用场景

1.实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
2.开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
3.死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。

4.CountDownLatch的源码解读

4.1 CountDownLatch的构造方法入手

在这里插入图片描述
在这里插入图片描述

4.2 countDownLatch.await();获取锁

在这里插入图片描述
在这里插入图片描述
进入CountDownLatch类的tryAcquireShared方法。会执行tryAcquireShared(arg)方法,doAcquireSharedInterruptibly(arg)方法

4.2.1 tryAcquireShared();尝试获取锁
// 参数无用
protected int tryAcquireShared(int acquires) {
	// 判断锁的状态是否等于0,如果等于0,说明获取到锁了。如果不等于0,获取不到锁。
   return (getState() == 0) ? 1 : -1;
}

图解
在这里插入图片描述

4.2.2 doAcquireSharedInterruptibly();进入阻塞
// arg 在此基本无用
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);
                    // 这些逻辑在CountDownLatch无任何意义,AQS有很多代码复用,但是在此时没有意义。
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 如果获取到挂起。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

图解:
在这里插入图片描述

4.3 countDownLatch.countDown();释放锁

在这里插入图片描述
在这里插入图片描述

4.3.1 tryReleaseShared;尝试释放锁
// 尝试释放锁,参数无用
protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
            	// 获取当前锁的数量
                int c = getState();
                // 如果再次锁的数量等于0,说明队列中已经没有被阻塞的线程了,无需唤醒。
                if (c == 0)
                    return false;
                    // 否则锁的数量-1
                int nextc = c-1;
                // 设置成新的值
                if (compareAndSetState(c, nextc))
                    // 再次判断是否等于0,此时等于0的话,需要去唤醒队列中的线程。不等于0不需要。
                    return nextc == 0;
        }
 }

图解:
在这里插入图片描述

4.3.2 doReleaseShared;尝试唤醒
private void doReleaseShared() {
       // cas 自旋锁
        for (;;) {
     		// 获取头部节点
            Node h = head;
            if (h != null && h != tail) {
                // 
                int ws = h.waitStatus;
                // 去唤醒阻塞节点,CountDownLatch 一般只有一个需要被唤醒的节点
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                // 在CountDownLatch基本无用
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

在CountDownLatch,此处带的作用,仅仅是唤醒阻塞的主线程。因为代码复用,所以很多代码基本无用。

4.CountDownLatch的执行流程

CountDownLatch 是保证子线程执行完后执行主线程。因为执行的时间不可确定,所以就有两种情况会发生。第一种,主线程被阻塞,子线程来唤醒。第二种,子线程直接执行完了,组线程没有去被阻塞。无论是哪种,都不影响最终的结果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值