首页 文章 精选 留言 我的

精选列表

搜索[MSA技术],共10000篇文章
优秀的个人博客,低调大师

测试用例设计方法六脉神剑——第四剑:石破天惊,功能图法攻阵| 京东物流技术团队

1 引言 前面几篇文章为我们讲述了因果图、判定表、正交试验等几种方法,主要是针对于不同条件输入输出的组合进行测试,但在实际需求中,我们也常会遇到需要对被测对象的状态流转进行验证的情况,此时前面几种方法将不再适用,对于这种状态转换类问题,功能图法则可大展身手。 2 概念及原理 2.1 基本概念 功能图:也称状态图法,该方法是一种专门解决动态说明问题的测试用例设计方法 ,主要由状态迁移图和逻辑功能模型两部分构成。 2.1.1 状态迁移图 用于描述一个特定对象生命期中满足某些条件的所有状态,以及由于各种事件的发生而引起的状态之间的转移。关于状态迁移图,有如下几点需要注意: 1)一个状态迁移图中包含如下几种状态: • 初态(即初始状态) ——实心圆 • 终态(即最终状态) ——一对同心圆(内圆为实心圆) • 中间状态——圆角矩形 2)在一张状态图中只能有一个初态,而终态则可以有0至多个。 3)状态图既可以表示单程生命周期,也可以表示系统循环运行过程。 2.1.2 逻辑功能模型 用于描述静态说明,表示在状态中输入条件和输出条件之间的对应关系。 2.2 核心思想 功能图法的核心思想在于:抽象出待测系统的若干状态以及状态之间的转换条件和转换路径,然后从状态迁移路径覆盖的角度设计测试用例。 3 方法步骤 1)分析需求,明确状态节点,具体关注以下几个信息 • 存在的状态; • 状态之间的转换关系; • 状态变化的触发条件。 2)梳理不同状态的转换,输出状态-条件表; 3)画出状态迁移图; • 定义初始状态; • 为初始状态增加一次操作改变初始状态,增加新的状态; • 为上一步步产生的新状态增加一次操作,再增加新的状态; • 循环直到没有新状态产生为止。 4)转换为状态迁移树; 结合广度优先遍历+深度优先遍历算法,遍历状态迁移图的每一条路径,得到状态迁移树。 5)从状态迁移树导出测试路径。 状态迁移树中根节点到每个叶子节点的路径即为一条测试用例。 4 举个栗子 场景:针对客服系统事件状态流转过程设计测试用例 1)分析客服事件可能存在的所有状态。 • 已创建/待分配 • 已分派 • 处理中 • 已解决 • 已关闭 • 升级待分派 • 再次打开  2)梳理状态之间的转换关系和触发条件,得到状态-条件表。  当前状态 下一个状态 状态转换条件 已创建/待分配 已分派 ①系统自动分派②手动领取 已分派 处理中 ①客服操作事件/事件有更新 已创建/待分配 处理中 ①事件转移 已创建/待分配 已解决 ①系统解决事件(如小程序-用户取消服务) 已创建/待分配 已关闭 ①mq触发关闭(如上游取消异常) 处理中 已解决 ①客服手动解决事件②系统自动解决事件 已解决 已关闭 ①归档 已解决 再次打开 ①手动reopen②系统触发reopen 处理中 升级待分派 ①客服升级事件 处理中 已关闭 ①合并 升级待分派 已分派 ①系统自动分派②手动领取 升级待分派 处理中 ①事件转移 再次打开 升级待分派 ①客服升级事件 再次打开 已解决 ①客服手动解决事件②系统自动解决事件 - 已创建/待分配 ①fxm/小程序等生成事件 - 已分派 ①客服手动创建②电话/咚咚等方式进线 - 升级待分派 ①系统升级事件(如邮政投诉事件) 3)根据状态-条件表,画出功能图/状态图。   4)根据功能图输出状态迁移树。   5)输出测试用例,下表举例输出前5条,其余类似不做赘述。  用例编号 测试项 标题 预置条件 操作步骤 预期结果 Test_01 改变事件状态 事件状态转变:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【已关闭】 可正常操作事件 1、通过fxm渠道进线生成事件;2、客服手动领取事件;3、客服操作更新事件信息;4、客服点击解决事件;5、事件15天无操作,系统归档事件。 事件状态转变为:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【已关闭】 Test_02 改变事件状态 事件状态转变:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【再次打开】--【已解决】--【已关闭】 可正常操作事件 1、通过fxm渠道进线生成事件;2、客服手动领取事件;3、客服操作更新事件信息;4、客服点击解决事件;5、客服reopen事件;6、客服点击解决事件;7、事件15天无操作,系统归档事件。 事件状态转变为:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【再次打开】--【已解决】--【已关闭】 Test_03 改变事件状态 事件状态转变:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【再次打开】--【升级待分派】--【已分派】--【处理中】--【已解决】--【已关闭】 可正常操作事件 1、通过fxm渠道进线生成事件;2、客服手动领取事件;3、客服操作更新事件信息;4、客服点击解决事件;5、客服reopen事件;6、客服点击升级事件;7、事件分派至其他客服;8、客服操作更新事件信息;9、客服解决事件;10、事件15天无操作,系统归档事件。 事件状态转变为:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【再次打开】--【升级待分派】--【已分派】--【处理中】--【已解决】--【已关闭】 Test_04 改变事件状态 事件状态转变:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【再次打开】--【升级待分派】--【处理中】--【已解决】--【已关闭】 可正常操作事件 1、通过fxm渠道进线生成事件;2、客服手动领取事件;3、客服操作更新事件信息;4、客服点击解决事件;5、客服reopen事件;6、客服点击升级事件;7、主管转移事件;8、客服处理并解决事件;9、事件15天无操作,系统归档事件。 事件状态转变为:【已创建/待分配】--【已分派】--【处理中】--【已解决】--【再次打开】--【升级待分派】--【处理中】--【已解决】--【已关闭】 Test_05 改变事件状态 事件状态转变:【已创建/待分配】--【已分派】--【处理中】--【升级待分派】--【已分派】--【处理中】--【已解决】--【已关闭】 可正常操作事件 1、通过fxm渠道进线生成事件;2、客服手动领取事件;3、客服操作更新事件信息;4、客服点击升级事件;5、事件分派至其他客服;6、客服操作更新事件信息;7、客服解决事件;8、事件15天无操作,系统归档事件。 事件状态转变为:【已创建/待分配】--【已分派】--【处理中】--【升级待分派】--【已分派】--【处理中】--【已解决】--【已关闭】 ... ... ... ... ... ...  5 总结  功能图/状态图方法 适用场景 1、由于某种条件成立导致发生状态改变的情况。2、主要关注状态转移的正确性。 优点 1、通过状态图可以清晰掌握系统的整个交互过程;2、可保证每一个状态的所有可达状态都覆盖到;3、通过验证给定条件内是否能够产生需要的状态变化,可检验出是否存在不可达的状态、不必要的状态或其他非法状态,以及非法的状态迁移。 缺点 针对有效输入输出进行设计,所以无法覆盖无效路径和非法输入。 注意事项 1、每种状态至少需要访问一次;2、重点测试最常见、最普遍的状态转换;3、其次测试最不常用的状态转换路径;4、单个状态之间的转换可通过结合其他用例设计方法保证覆盖全面;5、添加非法测试路径进行测试。(异常输入、状态和条件的非法组合)  通过本节讲述的功能图测试用例设计方法,我们可以轻松设计存在对象状态转移变换需求的测试用例,从而清楚的掌握整个系统的交互过程,但如果我们面对的需求中更多描述复杂的业务处理流程,我们又该使用哪种方法呢?一起看看下一节的场景法吧~ 作者:京东物流 陈雨婷 来源:京东云开发者社区 自猿其说Tech 转载请注明来源

优秀的个人博客,低调大师

ChatGPT小型平替之ChatGLM-6B本地化部署、接入本地知识库体验 | 京东云技术团队

本文期望通过本地化部署一个基于LLM模型的应用,能让大家对构建一个完整的应用有一个基本认知。包括基本的软硬环境依赖、底层的LLM模型、中间的基础框架及最上层的展示组件,最终能达到在本地零编码体验的目的。 一、ChatGLM-6B模型介绍 https://github.com/THUDM/ChatGLM-6B [Star 27.6k] 一个清华开源的、支持中英双语的对话语言模型,基于GLM架构,62亿参数。可以本地安装部署运行在消费级的显卡上做模型的推理和训练。 开源10天10000stars 当天在GitHub的趋势排行第一 Huggingface下载超过100万 开源的训练数据量达到1万亿字符的模型 1、能力 自我认知 文案写作 提纲写作 信息抽取 2、缺点 模型容量小导致回答存在偏见内容,推理能力较弱 3、场景 垂直领域知识 基于私有数据的问答 二、部署体验 1、环境依赖: 硬件: 基于GPU,建议16GB起步,建议24GB+体验,否则多轮容易爆显存; 无GPU可以仅用CPU,大约需要25GB内存,CPU会慢一点,本次不使用。 软件: CUDA 11.7+ Python3.10.8+ pip3 git 2、钞能力-算力市场: 强烈不建议本地部署,一整套硬件价值不菲,费时耗力,按需购买算力对于体验来说最划算,建议使用京东Ku+平台即可,目前都有配额,可直接申请使用,本次演示流程基于autodl算力平台搭建: ▪http://kuplus.jd.com [Ku+] ▪https://www.autodl.com [AutoDL] 3、下载demo: # 下载项目源代码 git clone https://github.com/THUDM/ChatGLM-6B # 切换到项目根目录 cd ChatGLM-6B # 安装依赖 pip install -r requirements.txt # 安装回老版本gradio,解决输出内容未渲染html标签问题 pip install gradio==3.28.3 # 如果pip下载有问题,修改其他源,比例douban或aliyun,没有pip.conf需新建 mkdir ~/.pip && touch ~/.pip/pip.conf && vim ~/.pip/pip.conf [global] index-url=http://pypi.douban.com/simple/ [install] trusted-host=pypi.douban.com 4、下载模型: # 直接git下载模型,大约需要13G空间 git clone https://huggingface.co/THUDM/chatglm-6b # 或者部分地区可能无法下载,可以使用镜像单独下载,新建url.txt, 拷贝以下镜像地址保存 https://cloud.tsinghua.edu.cn/seafhttp/files/08ff8050-912c-47b8-ad5c-56bcd231df71/ice_text.model https://cloud.tsinghua.edu.cn/seafhttp/files/88a7978c-8eda-498d-85e8-0671294a4c47/pytorch_model-00001-of-00008.bin https://cloud.tsinghua.edu.cn/seafhttp/files/de998e41-7093-41b3-84f1-59e32361e703/pytorch_model-00002-of-00008.bin https://cloud.tsinghua.edu.cn/seafhttp/files/077b5058-5ebd-4930-9470-8e873c01f47c/pytorch_model-00003-of-00008.bin https://cloud.tsinghua.edu.cn/seafhttp/files/728f7324-ce96-44bc-a08f-5c7222727ca5/pytorch_model-00004-of-00008.bin https://cloud.tsinghua.edu.cn/seafhttp/files/932b40ac-d195-4ba4-8d06-7946b8e6d0d8/pytorch_model-00005-of-00008.bin https://cloud.tsinghua.edu.cn/seafhttp/files/d763678e-c438-416b-a4b9-5594c52cb3f6/pytorch_model-00006-of-00008.bin https://cloud.tsinghua.edu.cn/seafhttp/files/59da96fd-8eb9-42d4-992e-03e1d0637a01/pytorch_model-00007-of-00008.bin https://cloud.tsinghua.edu.cn/seafhttp/files/849d7de4-9253-487a-bc37-9a43418e3c0c/pytorch_model-00008-of-00008.bin # 此处文件夹名称使用THUDM/chatglm-6b mkdir -p THUDM/chatglm-6b && cd THUDM/chatglm-6b && touch url.txt # 保存批量下载地址 vim url.txt # 执行wget批量下载到当前文件夹 wget -i url.txt 5、安装运行: 打开 web_demo.py # 一、如果模型位置不是默认path,修改如下位置: tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True) model = AutoModel.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True).half().cuda() # 二、找到最后一行或者类似 queue().launch 位置,指定ip、端口方便外网映射用,一般算力市场会默认提供一个映射端口,指定即可: demo.queue().launch(share=False, inbrowser=True, server_name='0.0.0.0', server_port=6006) # 启动 python web_demo.py root@autodl-container-9494499a62-3e5ab6d1:~/ChatGLM-6B# python web_demo.py Explicitly passing a `revision` is encouraged when loading a model with custom code to ensure no malicious code has been contributed in a newer revision. Explicitly passing a `revision` is encouraged when loading a configuration with custom code to ensure no malicious code has been contributed in a newer revision. Explicitly passing a `revision` is encouraged when loading a model with custom code to ensure no malicious code has been contributed in a newer revision. Loading checkpoint shards: 100%|█████████████████████████████████████████████████████████████████████| 8/8 [00:10<00:00, 1.36s/it] Running on local URL: http://0.0.0.0:6006 To create a public link, set `share=True` in `launch()`. 6、实际效果、资源占用: 主机配置 演示效果  7、推理参数含义: · Maximum length 通常用于限制输入序列的最大长度,因为ChatGLM-6B是2048长度推理的,一般这个保持默认就行, 太大可能会导致性能下降 · Top P Top P 参数是指在生成文本等任务中,选择可能性最高的前P个词的概率累加和。这个参数被称为Top P,也称为Nucleus Sampling。 例如,如果将Top P参数设置为0.7,那么模型会选择可能性排名超过70%的词进行采样。这样可以保证生成的文本准确性较高,但可能会缺乏多样性。相反,如果将Top P参数设置为0.3,则会选择可能性超过30%的词进行采样,这可能会导致生成文本的准确性下降,但能够更好地增加多样性。 · Temperature Temperature参数通常用于调整softmax函数的输出,用于增加或减少模型对不同类别的置信度。 具体来说,softmax函数将模型对每个类别的预测转换为概率分布。Temperature参数可以看作是一个缩放因子,它可以增加或减少softmax函数输出中每个类别的置信度。 例如,将Temperature设置为0.05和0.95的主要区别在于,T=0.05会使得模型更加自信,更加倾向于选择概率最大的类别作为输出,而T=0.95会使得模型更加不确定,更加倾向于输出多个类别的概率值较大。 8、其他好玩的: 看图说话,VisualGLM-6B,一个支持图像理解的多模态对话语言模型,语言模型基于ChatGLM-6B。 三、结合LangChain实现本地知识库 1、LangChain: https://github.com/hwchase17/langchain [Star 45k] ⽤于开发由语⾔模型驱动的应⽤程序的基础框架,是一个链接面向用户程序和LLM之间的中间层,可以理解成是一个lib。 · 简介原理 · 主要功能 1.调⽤语⾔模型; 2.将不同数据源接⼊到语⾔模型的交互中; 3.允许语⾔模型与运⾏环境交互。 · 模块介绍 1.Modules:⽀持的模型类型和集成; 2.Prompt:提示词管理、优化和序列化; 3.Memory:内存是指在链/代理调⽤之间持续存在的状态; 4.Indexes:当语⾔模型与特定于应⽤程序的数据相结合时,会变得更加强⼤-此模块包含⽤于加载、查询和更新外部数据的接⼝和集成; 5.Chain:链是结构化的调⽤序列(对LLM或其他实⽤程序); 6.Agents:代理是⼀个链,其中LLM在给定⾼级指令和⼀组⼯具的情况下,反复决定操作,执⾏操作并观察结果,直到⾼级指令执行完成; 7.Callbacks:回调允许您记录和流式传输任何链的中间步骤,从⽽轻松观察、调试和评估应⽤程序的内部。 2、LangChain-ChatGLM https://github.com/imClumsyPanda/langchain-ChatGLM [Star 8k] 一种利用langchain实现的基于本地知识库的问答应用,目标期望建立一套对中文场景与开源模型支持友好、可离线运行的知识库问答解决方案。 实现过程:包括加载文件 -> 读取文本 -> 文本分割 -> 文本向量化 -> 问句向量化 -> 在文本向量中匹配出与问句向量最相似的top k个 -> 匹配出的文本作为上下文和问题一起添加到prompt中 -> 提交给LLM生成回答。 · 项⽬特点 1.依托ChatGLM等开源模型实现,可离线部署; 2.基于langchain实现,可快速实现接⼊多种数据源; 3.在分句、⽂档读取等⽅⾯,针对中⽂使⽤场景优化; 4.⽀持pdf、txt、md、docx等⽂件类型接⼊,具备命令⾏demo、webui和vue前端。 · 项⽬结构 1.models:llm的接⼝类与实现类,针对开源模型提供流式输出⽀持; 2.loader:⽂档加载器的实现类; 3.textsplitter:⽂本切分的实现类; 4.chains:⼯作链路实现,如chains/local_doc_qa实现了基于本地⽂档的问答实现; 5.content:⽤于存储上传的原始⽂件; 6.vector_store:⽤于存储向量库⽂件,即本地知识库本体; 7.configs:配置⽂件存储。 · 安装部署 与ChatGLM安装基本类似,git clone之后修改模型地址到本地chartglm-6b的path启动即可,如果使用AutoDL平台,可用镜像一键拉起,镜像保持最新master版本,可体验最新功能。 最新镜像地址,已包含离线llm model: chatglm-6b 和 embedding: text2vec-large-chinese: https://www.codewithgpu.com/i/Liudapeng/langchain-ChatGLM/langchain-chatglm-master # 新增一步,下载离线 embedding model sudo apt-get install git-lfs git lfs install git clone https://huggingface.co/GanymedeNil/text2vec-large-chinese # 启用本地离线模型 vim ~/langchain-ChatGLM/configs/model_config.py # 1、修改 embedding path embedding_model_dict = { "text2vec": "/root/GanymedeNil/text2vec-large-chinese", } # 2、修改 llm path, 直接指向为第一章节的 chatglm-6b path即可 "chatglm-6b": { "name": "chatglm-6b", "pretrained_model_name": "THUDM/chatglm-6b", "local_model_path": "/root/ChatGLM-6B/THUDM/chatglm-6b", "provides": "ChatGLM" } # 3、启用本地模型 NO_REMOTE_MODEL = True # 运行Gradio web demo cd ~/langchain-ChatGLM/ root@autodl-container-95b111aeb0-c6fdac9f:~/langchain-ChatGLM# python webui.py INFO 2023-06-08 14:10:58,531-1d: loading model config llm device: cuda embedding device: cuda dir: /root/langchain-ChatGLM flagging username: dc706ff885da43a8b0e8181bcc72ad1e WARNING: OMP_NUM_THREADS set to 14, not 1. The computation speed will not be optimized if you use data parallel. It will fail if this PaddlePaddle binary is compiled with OpenBlas since OpenBlas does not support multi-threads. PLEASE USE OMP_NUM_THREADS WISELY. Loading chatglm-6b... Loading checkpoint shards: 100%|██████████████████████████████████████████████████████████████████████████████| 8/8 [00:13<00:00, 1.66s/it] Loaded the model in 23.48 seconds. INFO 2023-06-08 14:11:26,780-1d: Load pretrained SentenceTransformer: /root/GanymedeNil/text2vec-large-chinese {'answer': '你好👋!我是人工智能助手 ChatGLM-6B,很高兴见到你,欢迎问我任何问题。'} INFO 2023-06-08 14:11:33,694-1d: 模型已成功加载,可以开始对话,或从右侧选择模式后开始对话 Running on local URL: http://0.0.0.0:6006 To create a public link, set `share=True` in `launch()`. · 效果对比 LLM回答 LLM+知识库 知识库来源 四、视图框架 快速构建针对人工智能的python的webApp库,封装前端页面+后端接口+AI算法模型推理,方便AI算法工程师快速展示成果,常用的两个展示层框架: 1、Gradio: 优势在于易用性,代码结构相比Streamlit简单,只需简单定义输入和输出接口即可快速构建简单的交互页面,更轻松部署模型。适合场景相对简单,想要快速部署应用的开发者; 2、Streamlit: 优势在于可扩展性,相比Gradio复杂。适合场景相对复杂,想要构建丰富多样交互页面的开发者。 作者:京东科技 刘大朋 来源:京东云开发者社区

优秀的个人博客,低调大师

☕【Java深层系列】「并发编程系列」让我们一起探索一下CountDownLatch的技术原理和源码分析

CountDownLatch工作原理分析 一、大致介绍 那么本篇文章和大家分享分析一下JDK1.8的CountDownLatch的工作原理; 简单认识CountDownLatch 何为CountDownLatch? CountDownLatch从字面上理解,count计数做down的减法动作,而Latch又是门闩的意思; CountDownLatch是一种同步帮助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成; CountDownLatch内部没有所谓的公平锁\非公平锁的静态内部类,只有一个Sync静态内部类,CountDownLatch内部基本上也是通过sync.xxx之类的这种调用方式的; CountDownLatch内部维护了一个虚拟的资源池,如果许可数不为为0一直线程阻塞等待,直到许可数为0时才释放继续往下执行; CountDownLatch的state关键词 其实CountDownLatch的实现也恰恰很好利用了其父类AQS的state变量值; 初始化一个数量值作为计数器的默认值,假设为N,那么当任何线程调用一次countDown则计数值减1,直到许可为0时才释放等待; CountDownLatch,简单大致意思为:A组线程等待另外B组线程,B组线程执行完了,A组线程才可以执行; 常用重要的方法 创建一个给定许计数值的计数同步器对象 public CountDownLatch(int count) 入队等待,直到计数器值为0则释放等待 public void await() 释放许可,计数器值减1,若计数器值为0则触发释放无用结点 public void countDown() 获取目前最新的共享资源计数器值 public long getCount() 设计与实现伪代码 获取共享锁: 如果检测中断状态发现被中断过的话,那么则抛出InterruptedException异常 如果尝试获取共享锁失败的话( 尝试获取共享锁的各种方式由AQS的子类实现 ), 那么就新增共享锁结点通过自旋操作加入到队列中,然后通过调用LockSupport.park进入阻塞等待,直到计数器值为零才释放等待 public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } 释放共享锁: 如果尝试释放共享锁失败的话( 尝试释放共享锁的各种方式由AQS的子类实现 ), 那么通过自旋操作完成阻塞线程的唤起操作 public void countDown() { sync.releaseShared(1); } CountDownLatch生活细节化理解 比如百米赛跑,我就以赛跑为例生活化阐述该CountDownLatch原理: 1、场景:百米赛跑十人参赛,终点处有一个裁判计数; 2、开跑一声起跑信号,十个人争先恐后的向终点跑去,真的是振奋多秒,令人振奋; 3、当一个人到达终点,这个人就完成了他的赛跑事情了,就没事一边玩去了,那么裁判则减去一个人; 4、随着人员陆陆续续的都跑到了终点,最后裁判计数显示还有0个人未到达,意思就是人员都达到了; 5、然后裁判就拿着登记的成绩屁颠屁颠去输入电脑登记了; 6、到此打止,这一系列的动作认为是A组线程等待另外其他组线程的操作,直到计数器为零,那么A则再干其他事情; 源码分析CountDownLatch CountDownLatch构造器 构造器源码: /** * Constructs a {@code CountDownLatch} initialized with the given count. * * @param count the number of times {@link #countDown} must be invoked * before threads can pass through {@link #await} * @throws IllegalArgumentException if {@code count} is negative */ public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } 2、创建一个给定许计数值的计数同步器对象,计数器值必须大于零,count值最后赋值给了state这个共享资源值; Sync同步器 AQS --> Sync CountDownLatch内的同步器都是通过Sync抽象接口来操作调用关系的,细看会发现基本上都是通过sync.xxx之类的这种调用方式的; await() 源码 /** * Causes the current thread to wait until the latch has counted down to * zero, unless the thread is {@linkplain Thread#interrupt interrupted}. * * // 导致当前线程等待,直到计数器值减为零则释放等待,或者由于线程被中断也可导致释放等待; * * <p>If the current count is zero then this method returns immediately. * * <p>If the current count is greater than zero then the current * thread becomes disabled for thread scheduling purposes and lies * dormant until one of two things happen: * <ul> * <li>The count reaches zero due to invocations of the * {@link #countDown} method; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * the current thread. * </ul> * * <p>If the current thread: * <ul> * <li>has its interrupted status set on entry to this method; or * <li>is {@linkplain Thread#interrupt interrupted} while waiting, * </ul> * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * @throws InterruptedException if the current thread is interrupted * while waiting */ public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } await此方法被调用后,则一直会处于等待状态,其核心还是由于调用了LockSupport.park进入阻塞等待; 当计数器值state=0时可以打破等待现状,当然还有线程被中断后也可以打破线程等待现状; acquireSharedInterruptibly(int) 源码: /** * Acquires in shared mode, aborting if interrupted. Implemented * by first checking interrupt status, then invoking at least once * {@link #tryAcquireShared}, returning on success. Otherwise the * thread is queued, possibly repeatedly blocking and unblocking, * invoking {@link #tryAcquireShared} until success or the thread * is interrupted. * @param arg the acquire argument. * This value is conveyed to {@link #tryAcquireShared} but is * otherwise uninterpreted and can represent anything * you like. * @throws InterruptedException if the current thread is interrupted */ public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) // 调用之前先检测该线程中断标志位,检测该线程在之前是否被中断过 throw new InterruptedException(); // 若被中断过的话,则抛出中断异常 if (tryAcquireShared(arg) < 0) // 尝试获取共享资源锁,小于0则获取失败,此方法由AQS的具体子类实现 doAcquireSharedInterruptibly(arg); // 将尝试获取锁资源的线程进行入队操作 } 由于是实现同步计数器功能,所以tryAcquireShared首次调用必定小于0,则就顺利了进入了doAcquireSharedInterruptibly线程等待; 至于首次调用为什么会小于0,请看子类的实现,子类的实现判断为 "(getState() == 0) ? 1 : -1" ; 3.5、tryAcquireShared(int) 源码 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; // 计数器值与零比较判断,小于零则获取锁失败,大于零则获取锁成功 } 尝试获取共享锁资源,但是在计数器CountDownLatch这个功能中,小于零则需要入队,进入阻塞队列进行等待;大于零则唤醒等待队列,释放await方法的阻塞等待; doAcquireSharedInterruptibly(int) 源码 /** * Acquires in shared interruptible mode. * @param arg the acquire argument */ private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { // 按照给定的mode模式创建新的结点,模式有两种:Node.EXCLUSIVE独占模式、Node.SHARED共享模式; final Node node = addWaiter(Node.SHARED); // 创建共享模式的结点 boolean failed = true; try { for (;;) { // 自旋的死循环操作方式 final Node p = node.predecessor(); // 获取结点的前驱结点 if (p == head) { // 若前驱结点为head的话,那么说明当前结点自然不用说了,仅次于老大之后的便是老二了咯 int r = tryAcquireShared(arg); // 而且老二也希望尝试去获取一下锁,万一头结点恰巧刚刚释放呢?希望还是要有的,万一实现了呢 if (r >= 0) { // 若r>=0,说明已经成功的获取到了共享锁资源 setHeadAndPropagate(node, r); // 把当前node结点设置为头结点,并且调用doReleaseShared释放一下无用的结点 p.next = null; // help GC failed = false; return; } // 但是在await方法首次被调用会流转到此,这个时候获取锁资源会失败,即r<0,所以会进入是否需要休眠的判断 // 但是第一次进入休眠方法,因为被创建的结点waitStatus=0,所以会被修改一次为SIGNAL状态,再次循环一次 // 而第二次循环进入shouldParkAfterFailedAcquire方法时,返回true就是需要休眠,则顺利调用park方式阻塞等待 } if (shouldParkAfterFailedAcquire(p, node) && // 根据前驱结点看看是否需要休息一会儿 parkAndCheckInterrupt()) // 阻塞操作,正常情况下,获取不到共享锁,代码就在该方法停止了,直到被唤醒 // 被唤醒后,发现parkAndCheckInterrupt()里面检测了被中断了的话,则补上中断异常,因此抛了个异常 throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } } doAcquireSharedInterruptibly在实现计数器原理的时候,主要的干的事情就是等待再等待,等到计数器值为零时才苏醒; countDown() 源码 /** * Decrements the count of the latch, releasing all waiting threads if * the count reaches zero. * * <p>If the current count is greater than zero then it is decremented. * If the new count is zero then all waiting threads are re-enabled for * thread scheduling purposes. * * <p>If the current count equals zero then nothing happens. */ public void countDown() { sync.releaseShared(1); // 释放一个许可资源 } 释放许可资源,也就是计数器值不断的做减1操作,当计数器值为零的时候,该方法将会释放所有正在等待的线程队列;至于为什么还会释放所有,请看后续的releaseShared(int arg)讲解; releaseShared(int) 源码: /** * Releases in shared mode. Implemented by unblocking one or more * threads if {@link #tryReleaseShared} returns true. * * @param arg the release argument. This value is conveyed to * {@link #tryReleaseShared} but is otherwise uninterpreted * and can represent anything you like. * @return the value returned from {@link #tryReleaseShared} */ public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { // 尝试释放共享锁资源,此方法由AQS的具体子类实现 doReleaseShared(); // 自旋操作,唤醒后继结点 return true; // 返回true表明所有线程已释放 } return false; // 返回false表明目前还没释放完全,只要计数器值不为零的话,那么都会返回false } releaseShared方法首先就判断了tryReleaseShared(arg)的返回值,但是计数器值只要不为零,都会返回false,因此releaseShared该方法就立马返回false了; 所以当计数器值慢慢减至零时,则立马返回true,那么也就立马会调用doReleaseShared释放所有等待的线程队列; tryReleaseShared(int) 源码: // CountDownLatch 的静态内部类 Sync 类的 tryReleaseShared 方法 protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { // 自旋的死循环操作方式 int c = getState(); // 获取最新的计数器值 if (c == 0) // 若计数器值为零,说明已经通过CAS操作减至零了,所以在并发中读取到零时并不需要做什么操作,因此返回false return false; int nextc = c-1; // 计数器值减1操作 if (compareAndSetState(c, nextc)) // 通过CAS比较,顺利情况下设置成功返回true return nextc == 0; // 当通过计算操作得到的nextc为零时通过CAS修改成功,那么表明所有事情都已经做完,需要释放所有等待的线程队列 // 若CAS失败,想都不用想肯定是由于并发操作,导致CAS失败,那么唯一可做的就是下一次循环查看是否已经被其他线程处理了 } } CountDownLatch的静态内部类实现父类AQS的方法,用来处理如何释放锁,笼统的讲,若返回负数则需要进入阻塞队列,否则需要释放所有等待队列; doReleaseShared() 主要目的是释放线程中所有等待的队列,当计数器值为零时,此方法马上会被调用,通过自旋方式轮询干掉所有等待的队列; /** * Release action for shared mode -- signals successor and ensures * propagation. (Note: For exclusive mode, release just amounts * to calling unparkSuccessor of head if it needs signal.) */ private void doReleaseShared() { /* * Ensure that a release propagates, even if there are other * in-progress acquires/releases. This proceeds in the usual * way of trying to unparkSuccessor of head if it needs * signal. But if it does not, status is set to PROPAGATE to * ensure that upon release, propagation continues. * Additionally, we must loop in case a new node is added * while we are doing this. Also, unlike other uses of * unparkSuccessor, we need to know if CAS to reset status * fails, if so rechecking. */ for (;;) { // 自旋的死循环操作方式 Node h = head; // 每次都是取出队列的头结点 if (h != null && h != tail) { // 若头结点不为空且也不是队尾结点 int ws = h.waitStatus; // 那么则获取头结点的waitStatus状态值 if (ws == Node.SIGNAL) { // 若头结点是SIGNAL状态则意味着头结点的后继结点需要被唤醒了 // 通过CAS尝试设置头结点的状态为空状态,失败的话,则继续循环,因为并发有可能其它地方也在进行释放操作 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); // 唤醒头结点的后继结点 } // 如头结点为空状态,则把其改为PROPAGATE状态,失败的则可能是因为并发而被改动过,则再次循环处理 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } // 若头结点没有发生什么变化,则说明上述设置已经完成,大功告成,功成身退 // 若发生了变化,可能是操作过程中头结点有了新增或者啥的,那么则必须进行重试,以保证唤醒动作可以延续传递 if (h == head) // loop if head changed break; } } 总结 1、有了分析AQS的基础后,再来分析CountDownLatch便快了很多; 2、在这里我简要总结一下CountDownLatch的流程的一些特性: • 管理一个大于零的计数器值; • 每countDown一次则state就减1一次,直到许可证数量等于0则释放队列中所有的等待线程; • 也可以通过countDown/await组合一起使用,来实现CyclicBarrier的功能; CountDownLatch用法 CountDownLatch类只提供了一个构造器: public CountDownLatch(int count) { }; //参数count为计数值 然后下面这3个方法是CountDownLatch类中最重要的方法: public void await() throws InterruptedException { }; //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行 public void countDown() { }; //将count值减1 CountDownLatch, 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 举个例子说明: package main.java.CountDownLatch; import java.util.concurrent.CountDownLatch; public class countDownlatchTest { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(5); for(int i=0;i<5;i++){ new Thread(new readNum(i,countDownLatch)).start(); } countDownLatch.await(); System.out.println("线程执行结束。。。。"); } static class readNum implements Runnable{ private int id; private CountDownLatch latch; public readNum(int id,CountDownLatch latch){ this.id = id; this.latch = latch; } @Override public void run() { synchronized (this){ System.out.println("id:"+id); latch.countDown(); System.out.println("线程组任务"+id+"结束,其他任务继续"); } } } } 输出结果: id:1 线程组任务1结束,其他任务继续 id:0 线程组任务0结束,其他任务继续 id:2 线程组任务2结束,其他任务继续 id:3 线程组任务3结束,其他任务继续 id:4 线程组任务4结束,其他任务继续 线程执行结束。。。。 线程在countDown()之后,会继续执行自己的任务

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册