Juc并发编程14——线程计数器CountdownLatch源码剖析(下)

简介: 线程计数器文章目录线程计数器CountdownLatch源码剖析1 使用计数器锁实现任务计数2 await的源码剖析3 countdown源码剖析CountdownLatch源码剖析

接下来看看doAcquireShared是如何进行阻塞的

 private void doAcquireShared(int arg) {
    //向等待队列中添加一个新的共享锁节点
        final Node node = addWaiter(Node.SHARED); 
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) { // 无限循环
               // 获取当前新建节点的前驱节点
                final Node p = node.predecessor(); 
                //如果前驱节点是头节点,说明当前节点是等待队列的队首节点
                if (p == head) {
                  //再次尝试获取共享锁
                    int r = tryAcquireShared(arg);
                    if (r >= 0) { //获取成功
                       //将当前节点设置为头节点,并且继续唤醒后继节点
                        setHeadAndPropagate(node, r); 
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 和独占锁类似,没有获取到共享锁,挂起线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
              // 如果最后还没有获取到,直接cancel
                cancelAcquire(node);
        }
    }

原来阻塞就是挂起线程呀,我们其实早就知道了,只不过验证了下。而且上面的过程和独占锁其实很类似,不过在获取到节点后,不仅将当前线程设置成了头节点,而且还唤醒了后继节点。这就是共享锁的传播性:当前节点被唤醒后,后继节点也会被唤醒。这是因为可能不止一个线程调用了await方法进行等待。


究竟是如何进行的传播与唤醒呢?走进setHeadAndPropagate来一探究竟吧

 private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; //取出头节点
        setHead(node); //将当前节点设置为头节点
       //doAcquireShared中传参propagate一定大于0   
       //waitStatus初始为0,SIGNAL=-1;       
      // CONDITION = -2,PROPAGATE = -3;
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            // 取出当前节点的后继节点
            Node s = node.next;
            //共享模式或者s为空,继续doReleaseShared
            if (s == null || s.isShared())
              //继续唤醒下一个节点
                doReleaseShared(); 
        }
    }

原来就是判断是不是共享模式,是就继续调用doReleaseShared唤醒下一个节点,doReleaseShared后面会讲.

3 countdown源码剖析

了解完await的底层原理,这里我们接下来看下countdown方法的底层原理。看看它的底层调用方法

public void countDown() {
        sync.releaseShared(1);
}

点进releaseShared

    public final boolean releaseShared(int arg) {
      // 尝试释放锁,成功返回true
      // tryReleaseShared在前面讲await源码时讲过
      //只有当锁数量为0时才会释放成功
        if (tryReleaseShared(arg)) {
            // 继续唤醒后续节点
            doReleaseShared();
            return true;
        }
        return false;
    }

原来countdownawait最后都会调用doReleaseShared唤醒其它节点,前文留下的悬念是时候解开了,那就看看doReleaseShared

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            // 如果头节点不为空,而且头、尾节点不相同
            //说明等待队列中存在节点
            if (h != null && h != tail) {
              // 获取头节点的等待状态
                int ws = h.waitStatus;
                // 如果是SIGNAL(表示后继节点被挂起),
                //就将头节点的状态设置为初始值
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue; //失败就重来          
                    // 当头节点被唤醒,会唤醒头节点的后继节点
                    unparkSuccessor(h);
                }
                // 如果头节点的等待状态是初始状态0
                // 尝试将其状态设置为PROPAGATE(表示后继节点已经被唤醒)
                //PROPAGATE状态在setHeadAndPropagate中用到
                //可以让唤醒操作向后继节点传播
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;         // loop on failed CAS
            }
            if (h == head)         // loop if head changed
                break;
        }
    }

可能看下来觉得还是比较凌乱,没关系,我们可以回过头对本文章的源码与本专栏的AQS源码多读几遍,这里也梳理下,方便大家理解。

  • 共享锁是线程共享的,同一个时刻可能有多个线程拥有共享锁。
  • 如果一个线程刚获取到了共享锁,那么在其之后等待的线程很有可能也能够获取到共享锁,因此需要传播唤醒后继节点
  • 如果一个线程刚刚释放了线程锁,那么无论是共享锁还是独占锁,都需要传播唤醒后继节点。
相关文章
|
5天前
|
SQL 开发框架 .NET
高级主题:Visual Basic 中的多线程和并发编程
【4月更文挑战第27天】本文深入探讨了Visual Basic中的多线程和并发编程,阐述了其基本概念,如何使用`System.Threading.Thread`类创建线程,以及借助`ThreadPool`、`Monitor`和`SyncLock`进行同步管理。文章还提到了多线程编程面临的挑战如竞态条件、死锁和资源竞争,并介绍了VB的异步编程、TPL和并发集合等高级技术。通过实例展示了多线程在文件处理、网络通信和图像处理中的应用,并给出了多线程编程的最佳实践。总之,理解并掌握VB的多线程和并发编程能有效提升应用程序的性能和响应能力。
|
2天前
|
Dart 前端开发 安全
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
【4月更文挑战第30天】本文探讨了Flutter中线程管理和并发编程的关键性,强调其对应用性能和用户体验的影响。Dart语言提供了`async`、`await`、`Stream`和`Future`等原生异步支持。Flutter采用事件驱动的单线程模型,通过`Isolate`实现线程隔离。实践中,可利用`async/await`、`StreamBuilder`和`Isolate`处理异步任务,同时注意线程安全和性能调优。参考文献包括Dart异步编程、Flutter线程模型和DevTools文档。
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
|
3天前
|
安全 调度 Swift
【Swift开发专栏】Swift中的多线程与并发编程
【4月更文挑战第30天】本文探讨Swift中的多线程与并发编程,分为三个部分:基本概念、并发编程模型和最佳实践。介绍了线程、进程、并发与并行、同步与异步的区别。Swift的并发模型包括GCD、OperationQueue及新引入的结构体Task和Actor。编写高效并发代码需注意任务粒度、避免死锁、使用线程安全集合等。Swift 5.5的并发模型简化了异步编程。理解并掌握这些知识能帮助开发者编写高效、安全的并发代码。
|
3天前
|
安全 Java 开发者
构建高效微服务架构:后端开发的新范式Java中的多线程并发编程实践
【4月更文挑战第29天】在数字化转型的浪潮中,微服务架构已成为软件开发的一大趋势。它通过解耦复杂系统、提升可伸缩性和促进敏捷开发来满足现代企业不断变化的业务需求。本文将深入探讨微服务的核心概念、设计原则以及如何利用最新的后端技术栈构建和部署高效的微服务架构。我们将分析微服务带来的挑战,包括服务治理、数据一致性和网络延迟问题,并讨论相应的解决方案。通过实际案例分析和最佳实践的分享,旨在为后端开发者提供一套实施微服务的全面指导。 【4月更文挑战第29天】在现代软件开发中,多线程技术是提高程序性能和响应能力的重要手段。本文通过介绍Java语言的多线程机制,探讨了如何有效地实现线程同步和通信,以及如
|
4天前
|
并行计算 数据处理 开发者
Python并发编程:解析异步IO与多线程
本文探讨了Python中的并发编程技术,着重比较了异步IO和多线程两种常见的并发模型。通过详细分析它们的特点、优劣势以及适用场景,帮助读者更好地理解并选择适合自己项目需求的并发编程方式。
|
8天前
|
Java
[并发编程基础] Java线程的创建方式
[并发编程基础] Java线程的创建方式
|
9天前
|
安全 Java API
Java从入门到精通:3.2.1分布式与并发编程——深入Java并发包,精通多线程高级用法
Java从入门到精通:3.2.1分布式与并发编程——深入Java并发包,精通多线程高级用法
|
3天前
|
监控 安全 Java
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
|
3天前
|
缓存 安全 Java
多线程--深入探究多线程的重点,难点以及常考点线程安全问题
多线程--深入探究多线程的重点,难点以及常考点线程安全问题
|
4天前
|
数据采集 安全 Java
Python的多线程,守护线程,线程安全
Python的多线程,守护线程,线程安全