java基础-并发编程-CountDownLatch(JDK1.8)源码学习

news2024/9/27 12:08:42

CountDownLatch方法调用与类关系图

在这里插入图片描述

一、初始化:public CountDownLatch(int count)

public CountDownLatch(int count) {
     if (count < 0) throw new IllegalArgumentException("count < 0");
     this.sync = new Sync(count);
 }
 
Sync(int count) {
	// 将参数**count**值赋值给AQS类中**private volatile int state**变量
    setState(count);
}

二、获取共享锁并将当前线程挂起(获取到共享锁时不挂起):public void await()

public void await() throws InterruptedException {
        // 此处获取可中断的共享锁
        sync.acquireSharedInterruptibly(1);
    }

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        // 如果线程已被中断,则抛出中断异常
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试获取共享锁,此钩子方法在java.util.concurrent.CountDownLatch.Sync类中实现
        if (tryAcquireShared(arg) < 0)
        	// 未获取到共享锁,则执行doAcquireSharedInterruptibly方法
            doAcquireSharedInterruptibly(arg);
    }   
protected int tryAcquireShared(int acquires) {
			// 当初始化时设置的state属性值为0(countDown方法中修改此值)时才认为可以直接获取到锁
            return (getState() == 0) ? 1 : -1;
        }
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // 添加一个共享节点到同步队列,此共享节点有以下特征
        // 1、该节点为同步队列尾节点
        // 2、该节点中封装了当前线程信息
        // 3、该节点此时属性waitStatus赋值为0
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                // 如果当前线程为第一次获取锁,则node节点时同步队列中第二个元素,因此p == head
                // 如果当前线程刚被唤醒,则node节点时同步队列中第二个元素,因此p == head
                if (p == head) {
                	// 再次尝试获取共享锁
                    int r = tryAcquireShared(arg);
                    // 当所有多线程都完成任务,调用countDown方法将state属性值设置为0时,r值为1
                    // 当一部分线程完成任务,调用countDown方法后state属性值不为0,r值为-1
                    if (r >= 0) {
                    	// 将当前节点node设置为同步队列头节点,并传播
                        setHeadAndPropagate(node, r);
                        // 将前头节点head从当前前node上移除(上面已将前头节点head从当前前node上移除,此时前头节点完全从同步队列上移除)
                        p.next = null; // help GC
                        // 完成线程唤醒,failed 重新赋值false,不会在此方法最后执行cancelAcquire(node);
                        failed = false;
                        return;
                    }
                }
                // 如果同步队列中node前面还有其他等待节点,则将node节点中属性waitStatus赋值为-1,此时返回false,不会将该线程挂起
                // shouldParkAfterFailedAcquirenode一般执行两遍,那么很有可能第二遍的时候,发现自己的前驱突然变成head了并且获取共享锁成功,又或者本来第一遍的前驱就是head但第二遍获取共享锁成功了
                if (shouldParkAfterFailedAcquire(p, node) &&
                	// 执行完前一个判断后再循环一次,如果同步队列中node前面还有其他等待节点,则挂起该线程
                	// 线程调用两次shouldParkAfterFailedAcquire,和一次parkAndCheckInterrupt后,便阻塞了。之后就只能等待别人unpark自己了
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
        	// 线程被中断,抛出异常,此时failed为初始值true,取消获取锁
            if (failed)
                cancelAcquire(node);
        }
    }
    private Node addWaiter(Node mode) {
    	// 添加一个共享节点到同步队列
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
        	// 尾节点不为空,将尾节点设置为node前节点
            node.prev = pred;
            // 将node节点设置为尾节点
            if (compareAndSetTail(pred, node)) {
            	// 设置成功,则将node设置为前尾节点的后继节点
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
            	// 尾节点为null,头节点肯定也为空,此时使用空节点重置头节点
                if (compareAndSetHead(new Node()))
                	// 尾节点和头节点指向同一个节点
                    tail = head;
            } else {
            	// 将尾节点设置为node前节点
                node.prev = t;
                // 将node节点设置为尾节点
                if (compareAndSetTail(t, node)) {
                	// 设置成功,则将node设置为前尾节点的后继节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

AQS深入理解 setHeadAndPropagate源码分析 JDK8

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        // h保存了旧的head,但现在head已经变成node了
        setHead(node);
        // 如果同时有两个线程进入方法【doReleaseShared】,同时执行到【compareAndSetWaitStatus(h, Node.SIGNAL, 0)】
        // 其中只有一个执行unparkSuccessor,两一个将head节点waitStatus值修改为了-3,然后退出,此种情况在当前判断中传播唤醒下一节点
        // TODO
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

private void setHead(Node node) {
		// 将当前节点设置为头节点head
        head = node;
        // 头节点中线程为空,因为头节点中的线程已经被唤醒且执行完,使用空头节点存储待唤醒节点的状态
        node.thread = null;
        // 将前头节点从当前节点上移除
        node.prev = null;
    }
private void doReleaseShared() {
        
        for (;;) {
            Node h = head;
            // 同步队列中存在待唤醒的线程
            // h != null: 头节点为空节点
            // h != tail:同步队列刚初始化,还没有待唤醒的线程
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // head节点的后继节点待唤醒
                if (ws == Node.SIGNAL) {
                	// CAS修改头节点waitStatus值为0,修改失败再次循环 ——①
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 修改成功,唤醒后继节点
                    unparkSuccessor(h);
                }
               // 若①处执行成功后线程被打断,此时ws为0,此时将修改头节点waitStatus值为-3
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            // 如果此时head节点已被修改,则可以继续唤醒下一个节点
            // 如果此时head节点未被修改,则跳出循环
            if (h == head)                   // loop if head changed
                break;
        }
    }
private void unparkSuccessor(Node node) {
		// TODO
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        // 如果下一个节点不存在或下一个节点线程被中断(s.waitStatus == 1)
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 跳过该节点,从尾节点向前遍历
            for (Node t = tail; t != null && t != node; t = t.prev)
                // 找到不是node节点(头节点)的最前面一个待唤醒节点
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

三、递减闩锁的计数:public void countDown()

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

public final boolean releaseShared(int arg) {
		// 尝试释放共享锁
        if (tryReleaseShared(arg)) {
        	// 尝试失败,释放共享锁(注解在上面)
            doReleaseShared();
            return true;
        }
        return false;
    }
protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            // state==0时门闩已打开,此时调用countDown无意义
            if (c == 0)
                return false;
            // state!=0, state-1,CAS设置给state
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1018915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pte初步认识学习

我们的时间的确很少&#xff0c;但是我们每天都乐意将珍贵的时间浪费在大量毫无意义的事情上 目录 pte介绍 PTE口语评分规则 pte架构 计算机科学23 QS排名 《芭比》 pte介绍 PTE口语评分规则 有抑扬顿挫 对于连读 不能回读 native pte对于个别单词没有读好&#xff0c…

JSP 学习笔记(基础)

出现背景&#xff1a; 由于 Servlet 输出 HTML 比较困难&#xff0c;所以出现了 JSP 这个代替品。 特点&#xff1a; 基于文本&#xff0c;HTML 和 Java 代码共同存在&#xff08;用 write() 来写 HTML 标签&#xff09;其本身就是个被封装后的 Servlet&#xff08;被编译为…

JavaScript的BOM操作

一、BOM 1.认识BOM BOM&#xff1a;浏览器对象模型&#xff08;Browser Object Model&#xff09; 简称 BOM&#xff0c;由浏览器提供的用于处理文档&#xff08;document&#xff09;之外的所有内容的其他对象&#xff1b;比如navigator、location、history等对象&#xff…

Android 使用Camera1实现相机预览、拍照、录像

1. 前言 本文介绍如何从零开始&#xff0c;在Android中实现Camera1的接入&#xff0c;并在文末提供Camera1Manager工具类&#xff0c;可以用于快速接入Camera1。 Android Camera1 API虽然已经被Google废弃&#xff0c;但有些场景下不得不使用。 并且Camera1返回的帧数据是NV21…

JSP ssm 零配件管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java ssm 零配件管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用…

ThinkPHP 5.0通过composer升级到5.1,超级简单

事情是这样的&#xff0c;我实现一个验证码登录的功能&#xff0c;但是这个验证码的包提示tp5的版本可以是5.1.1、5.1.2、5.1.3。但我使用的是5.0&#xff0c;既然这样&#xff0c;那就升个级呗&#xff0c;百度了一下&#xff0c;结果发现大部分都是讲先备份application和修改…

python之pyQt5实例:PyQtGraph的应用

1、显示逻辑 "MainWindow": "这是主窗口&#xff0c;所有的其他组件都会被添加到这个窗口上。", "centralwidget": "这是主窗口的中心部件&#xff0c;它包含了其他的部件。","pushButton": "这是一个按钮&#xff0c…

算法刷题 week3

这里写目录标题 1.重建二叉树题目题解(递归) O(n) 2.二叉树的下一个节点题目题解(模拟) O(h) 3.用两个栈实现队列题目题解(栈&#xff0c;队列) O(n) 1.重建二叉树 题目 题解 (递归) O(n) 递归建立整棵二叉树&#xff1a;先递归创建左右子树&#xff0c;然后创建根节点&…

贪心算法的思路和典型例题

一、贪心算法的思想 贪心算法是一种求解问题时&#xff0c;总是做出在当前看来是最好的选择&#xff0c;不从整体最优上加以考虑的算法。 二.用贪心算法的解题策略 其基本思路是从问题的某一个初始解出发一步一步地进行&#xff0c;根据某个优化测度&#xff0c;每一步都要确保…

Idea 下载不了源码 cannot download source

一、打开Terminal (AltF12)&#xff0c;找到项目具体模块所在的文件夹&#xff0c;输入一下指令 mvn dependency:resolve -Dclassifiersources 如果你的idea 终端无法使用mvn指令&#xff0c;要配置你idea中的maven的环境变量&#xff1a; 1、找到maven在idea中的位置&#xf…

Linux备份策略:保证数据安全

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

第十章 数据库恢复技术

第十章 数据库恢复技术 10.1 事务的基本概念 事务 事务是用户定义的一个数据库操作序列&#xff0c;这些操作要么全做&#xff0c;要么全不做&#xff0c;是一个不可分割的工作单位。例事务的特性&#xff08;ACID特性&#xff08;ACID properties&#xff09;&#xff09; 原…

【测试开发】概念篇 · 测试相关基础概念 · 常见开发模型 · 常见测试模型

【测试开发】概念篇 文章目录 【测试开发】概念篇1. 什么是需求1.1 需求的定义1.2 为什么有需求1.3 测试人员眼里的需求1.4 如何深入了解需求 2. 什么是测试用例2.1 为什么有测试用例2.2 练习>手机打电话 3. 什么是bug4. 开发模型和测试模型4.1 软件生命周期4.2 开发模型4.3…

七、线性规划问题

文章目录 1、线性规划问题定义2、单纯形算法THE END 1、线性规划问题定义 \qquad 线性规划问题的一般表示形式如下所示&#xff1a;假设现有 n n n个变量&#xff0c; m m m个约束&#xff0c;令最大化(或者最小化) c 1 x 1 c 2 x 2 . . . c n x n c_1x_1c_2x_2...c_nx_n c1…

IDEA中创建Java Web项目方法2

以下过程使用IntelliJ IDEA 2021.3 一、创建Maven项目 1. File -> New -> Projects... 2. 选择Maven&#xff0c;点击Next 3. 输入项目名称&#xff0c;Name: WebDemo3。点击 Finish&#xff0c;生成新的项目 二、添加框架支持 1. 在项目名上右键&#xff0c;选择 A…

快速搭建SpringBoot3.x项目

快速搭建SpringBoot3.x项目 写在前面一、创建项目二、配置多环境三、连接数据库查询数据3.1 新建数据库mybatisdemo并且创建sys_user表3.2 创建实体类3.2 创建Mapper接口3.3 添加mybatis.xml文件3.4 新建service 接口及实现类3.5 创建Controller 四、封装统一结果返回4.1 定义 …

计算平均值

任务描述 编程实现&#xff1a;编写程序实现如下功能&#xff1a;通过键盘&#xff0c;用指针输入10个元素的值&#xff0c;再通过指针计算各元素的平均值&#xff0c;输出平均值。 测试说明 平台会对你编写的代码进行测试&#xff1a; 测试样例1&#xff1a; 测试输入&…

applicationId和packageName 的异同

关于作者&#xff1a; CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP&#xff0c;带领广告团队广告单日营收超千万。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业化变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读…

安卓机型固件系统分区的基础组成 手机启动规律初步常识 各分区的基本含义与说明

此贴为基本常识。感兴趣的友友可以了解手机的启动顺序和各模式的基本操作与意义。另外了解手机系统分区各文件夹的含义 分区说明对应贴&#xff1a;安卓机型固件中分区对应说明 手机开机基本启动顺序 当我们按下手机开机键的时候。基本的启动顺序为 注意&#xff1a;该结构图…

基于matlab实现的多普勒频移海底混响点散射模型程序

完整程序&#xff1a; %有多普勒频移的海底混响点散射模型 clear all; close all; clc H100; %海水深度 D50; %合置声纳深度 c1500; %声速 azmpi/6; %水平方位角 u-27; %垂直散射系数 v20; %声…