-
在复杂的分布式系统中,传统的监控手段——如简单的指标采集和日志查看——已难以满足快速定位问题、理解系统行为和保障用户体验的需求。可观测性(Observability)作为新一代系统洞察方法论,正逐步取代传统监控,成为云原生时代运维与开发的核心能力。本文将深入探讨可观测性的三大支柱(日志、指标、链路追踪)如何协同工作,分析主流技术栈的选型逻辑,并分享构建高效、低成本、可扩展可观测性系统的工程实践。什么是可观测性?可观测性源于控制理论,指通过系统外部输出(如日志、指标)推断其内部状态的能力。在软件工程中,它强调无需修改代码即可理解系统运行时行为,尤其适用于黑盒或灰盒系统。与传统监控的区别在于:监控:基于已知问题设置告警(“已知的未知”)可观测性:支持探索未知问题(“未知的未知”)例如,当用户投诉“页面加载慢”,可观测性系统应能帮助工程师快速下钻:是某个数据库查询变慢?CDN 回源异常?还是第三方 API 延迟升高?可观测性的三大支柱1. 日志(Logs):记录离散事件日志是最原始的系统输出,通常为带时间戳的文本或结构化 JSON。用途:调试、审计、错误追踪挑战:数据量大、非结构化、检索成本高2. 指标(Metrics):聚合的数值序列指标是对系统状态的量化描述,如 CPU 使用率、HTTP 请求数、错误率。特点:高压缩比、低存储成本、适合告警局限:丢失上下文,无法还原具体请求示例:http_requests_total{method="POST", status="500", path="/api/order"}3. 链路追踪(Traces):请求的全链路视图追踪记录单个请求在多个服务间的流转路径,由一系列 Span 组成。核心价值:可视化调用拓扑、定位性能瓶颈、识别异常服务标准协议:OpenTelemetry(OTLP)已成为事实标准,取代早期的 Zipkin、Jaeger 协议。技术栈选型:开源 vs 托管开源自建方案日志:Fluent Bit → Kafka → Loki / Elasticsearch指标:Prometheus + Thanos(长期存储)追踪:OpenTelemetry Collector → Jaeger / Tempo可视化:Grafana(统一面板)优势:成本可控、数据自主、灵活定制代价:运维复杂、需处理扩缩容、高可用保障云托管服务AWS:CloudWatch Logs + X-Ray + Managed PrometheusGCP:Cloud Logging + Cloud Trace + Cloud Monitoring第三方:Datadog、New Relic、Splunk Observability优势:开箱即用、自动扩缩、集成 AI 异常检测代价:按量计费成本高,存在厂商锁定风险选型建议:初创团队/成本敏感 → 开源组合(Loki + Prometheus + Tempo + Grafana)中大型企业/追求效率 → 托管服务 + OpenTelemetry 标准接入统一数据采集:OpenTelemetry 的核心作用OpenTelemetry(OTel)是 CNCF 主导的可观测性标准,提供:语言 SDK:自动注入 trace_id、采集指标(支持 Java、Go、Python 等)Collector:接收、处理、转发 Logs/Metrics/Traces语义约定:统一字段命名(如 http.method, db.statement)典型架构:App (OTel SDK) → OTel Collector → Backend (Loki/Prometheus/Jaeger)通过 OTel,应用只需一次埋点,即可将数据分发至多个后端,避免 vendor lock-in。成本优化策略可观测性数据增长迅猛,需主动控制成本:采样(Sampling)对高频 Trace 按比例采样(如 10%)错误请求 100% 采集,确保关键路径不丢失日志分级生产环境关闭 DEBUG 日志WARN/ERROR 日志强制保留冷热分层存储热数据(7天):高性能存储(Elasticsearch)冷数据(30天+):对象存储(S3)+ 查询引擎(Athena)指标预聚合在 Collector 层对高基数指标进行降维(如合并相似 label)构建一个轻量级可观测性平台假设你正在搭建一个微服务系统,预算有限但需基本可观测能力:采集层应用集成 OTel SDK,自动注入 trace_id 到日志部署 Fluent Bit DaemonSet 采集容器日志传输层使用 OTel Collector 接收数据,做简单过滤和批处理存储与查询Logs → Grafana Loki(低成本,标签索引)Metrics → Prometheus + VictoriaMetrics(高压缩比)Traces → Grafana Tempo(与 Loki 共享对象存储)可视化Grafana 统一面板:日志搜索、指标图表、Trace 下钻联动该方案总资源占用可控制在 2–4 核 CPU + 8GB 内存,适合中小规模集群。常见误区与避坑指南误区1:“日志越多越好”→ 后果:存储爆炸、检索变慢。应聚焦关键路径日志。误区2:“有了 Prometheus 就够了”→ 后果:无法定位具体错误请求。需结合 Trace 和 Logs。误区3:“可观测性只是运维的事”→ 正确做法:开发需在代码中合理埋点(如记录业务关键事件)。误区4:忽略上下文关联→ 必须确保 Logs、Metrics、Traces 共享相同标识(如 trace_id、service.name)。 可观测性不是一堆工具的堆砌,而是一种以问题为导向的系统思维。优秀的可观测性系统,能让工程师在 5 分钟内回答:“用户为什么失败?”、“系统哪里变慢了?”、“这次发布是否引入了风险?”。随着 OpenTelemetry 的普及和云原生生态的成熟,构建高效、统一、低成本的可观测性平台已不再是巨头专属。无论团队规模大小,只要坚持结构化数据、统一上下文、分层存储和成本意识,就能让系统真正“可见、可查、可信赖”。毕竟,在复杂的世界里,看得见,才安心。
-
1. 检索增强生成(RAG, Retrieval-Augmented Generation)RAG 是当前大语言模型(LLM)落地的关键架构之一,其核心思想是:在生成答案前,先从外部知识库中检索相关上下文,再将上下文与用户问题一同输入 LLM,从而提升回答的准确性、时效性和可解释性。倒排索引的作用:对知识库文档(如 FAQ、产品手册、维基页面)进行分词并构建倒排索引用户提问时,提取关键词(或经 query expansion 后),通过倒排索引快速召回包含这些关键词的候选文档作为第一阶段粗排(Candidate Generation),高效过滤出 Top-K 相关文档(如 100 篇),供后续精排或向量重排使用优势:检索速度快,适合处理海量文本可解释性强(能明确看到命中了哪些关键词)对拼写错误、同义词可通过 Analyzer(如 IK 分词器 + 同义词库)优化2. 混合检索(Hybrid Search):关键词 + 语义的双重保障纯向量检索虽能理解语义相似性,但对精确术语、实体名、代码片段、ID 等字面匹配需求表现不佳。而倒排索引恰好擅长此类任务。混合策略:两路召回融合:一路用倒排索引做关键词匹配,一路用向量索引做语义匹配,再加权合并结果(如 Reciprocal Rank Fusion)级联过滤:先用倒排索引召回包含关键实体(如“iPhone 15”、“北京”)的文档,再在子集中做向量精排元数据过滤:利用倒排索引对文档的标签、类别、时间等结构化字段进行高效过滤应用场景:电商搜索:“红色连衣裙 2024新款” → 倒排索引确保“红色”“连衣裙”“2024”被精确命中,向量模型理解“新款”语义技术文档搜索:“如何修复 Kubernetes Pod CrashLoopBackOff” → 倒排索引精准捕获错误码,向量模型理解“修复”意图3. 多模态检索中的元数据索引在图像、视频、音频等多模态 AI 系统中,原始内容被编码为向量,但其关联的文本元数据(如标题、标签、OCR 识别结果、ASR 转录文本)仍需高效检索。倒排索引的角色:对图像的 OCR 文本、视频字幕、音频转写内容构建倒排索引用户输入文本查询时,可同时触发:向量路径:将 query 编码为向量,搜索视觉/听觉特征向量文本路径:通过倒排索引搜索元数据中的关键词二者结果融合,提升召回率案例:视频平台搜索“包含‘特斯拉发布会’字幕的视频” → 倒排索引快速定位字幕含该短语的视频片段医疗影像系统:通过报告中的“肺结节”“CT”等关键词召回相关病例,再结合影像向量比对4. 负样本挖掘与训练数据构建在训练对比学习(Contrastive Learning)或双塔模型时,需要大量难负样本(Hard Negative) 来提升模型判别能力。倒排索引辅助负采样:给定一个正样本 query,用倒排索引召回一批“看似相关但实际无关”的文档(如包含部分关键词但主题偏离)这些文档作为高质量负样本,比随机负样本更能提升模型性能5. 实时更新与高吞吐写入场景相比向量索引(如 FAISS、HNSW)通常需要批量重建或复杂增量更新,倒排索引天然支持高频写入和实时可见(如 Elasticsearch 的近实时 refresh)。AI 应用价值:用户行为日志、实时新闻、社交媒体内容等动态数据,可立即被倒排索引收录并参与检索在 RAG 系统中,新上传的文档几秒内即可被查询到,满足企业对“知识即时生效”的需求6. 可解释性与调试支持AI 系统常被视为“黑盒”,而倒排索引提供了透明的检索依据:可展示“因命中关键词‘退款政策’而推荐该文档”便于人工审核、规则干预(如屏蔽某些关键词结果)在合规场景(如金融、医疗)中满足审计要求
-
在数据驱动的时代,数据库作为信息系统的核心组件,其性能直接决定了应用的响应速度与用户体验。而索引(Index),作为加速数据检索的关键技术,堪称数据库的“高速公路”。不同的数据访问模式催生了多样化的索引结构——从关系型数据库广泛采用的 B+ 树,到 NoSQL 系统偏爱的 LSM 树(Log-Structured Merge-Tree),再到搜索引擎依赖的 倒排索引(Inverted Index)。理解这些索引的设计哲学、适用场景与实现细节,是构建高性能数据系统的基础。一、B+ 树:OLTP 场景的黄金标准1. 结构特点B+ 树是一种多路平衡查找树,专为磁盘 I/O 优化设计:所有数据存储在叶子节点,非叶子节点仅保存索引键和指针叶子节点通过链表串联,支持高效的范围查询节点大小通常设为一页(如 4KB 或 16KB),减少磁盘随机读取次数例如,在 MySQL InnoDB 中,主键索引即为聚簇 B+ 树,行数据直接存储在叶子节点;二级索引则为非聚簇 B+ 树,叶子节点存储主键值。2. 优势与适用场景点查高效:时间复杂度 O(logₙN),n 为分支因子(通常数百)范围扫描快:叶子节点链表顺序遍历,避免回溯写入稳定:每次更新仅修改少量页面,支持 WAL(Write-Ahead Logging)保证事务原子性3. 写放大与碎片问题频繁的随机插入可能导致页分裂,产生空间碎片;删除操作则留下“空洞”,需定期 OPTIMIZE TABLE 回收空间。二、LSM 树:为写密集型负载而生当写入吞吐成为瓶颈(如日志、时序数据、消息队列),B+ 树的随机写开销显得力不从心。LSM 树通过将随机写转化为顺序写,极大提升写入性能。1. 核心思想:分层合并LSM 树将数据分为多层(Level),典型结构如下:MemTable:内存中的有序结构(如 SkipList),接收新写入Immutable MemTable:写满后冻结,异步刷入磁盘SSTable(Sorted String Table):磁盘上的只读有序文件Compaction:后台合并小 SSTable 为大文件,清除过期/删除数据写入流程:数据先写入 WAL(保证持久性)插入 MemTable(内存操作,极快)MemTable 满后转为 Immutable,刷盘生成 Level 0 SSTable后台线程定期执行 Compaction,合并多层 SSTable2. 优势与代价写入吞吐极高:顺序写磁盘,接近硬件极限空间放大:Compaction 过程中临时占用额外空间读放大:需查询多层 SSTable 和 MemTable,可能触发多次 I/O写放大:同一数据在 Compaction 中被反复重写3. 优化策略布隆过滤器(Bloom Filter):快速判断 key 是否存在于某 SSTable,减少无效 I/O分层 Compaction(Leveled) vs 大小分级(Size-Tiered):权衡读写放大与空间效率三、倒排索引:全文搜索的基石在搜索引擎或需要文本匹配的场景中,传统索引无法高效支持“关键词 → 文档”的反向查找。倒排索引应运而生。1. 结构组成倒排索引由两部分构成:词典(Dictionary):所有唯一词条的有序列表,通常用 B+ 树或 FST(Finite State Transducer)存储倒排列表(Posting List):每个词条对应的文档 ID 列表,常附带位置、频率等元数据例如,对两篇文档建立索引:Doc1: "The quick brown fox"Doc2: "Jumped over the lazy dog"倒排列表:"the" → [Doc1(pos=0), Doc2(pos=2)]"fox" → [Doc1(pos=3)]...2. 高效压缩与跳表优化Delta 编码:文档 ID 通常递增,存储相邻差值(如 [100, 105, 110] → [100, 5, 5])跳表(Skip List):在长 Posting List 中设置跳跃指针,加速“AND/OR”查询的交并集计算3. 实时更新挑战传统倒排索引为静态构建。为支持实时写入,现代引擎(如 Elasticsearch)采用:分段(Segment)机制:新数据写入新 Segment,后台合并近实时(NRT)搜索:通过 refresh 操作使新数据可查(默认 1 秒)四、三大索引对比与选型指南特性B+ 树LSM 树倒排索引核心优势点查 & 范围查询快写入吞吐高文本关键词检索高效写入性能中等(随机写)极高(顺序写)中等(需分段合并)读取性能稳定低延迟可能较高(多层查找)依赖压缩与缓存事务支持强(MVCC + WAL)弱(通常最终一致)无典型系统MySQL, PostgreSQLCassandra, RocksDBElasticsearch, Lucene适用场景OLTP、高一致性业务日志、时序、IoT搜索、文本分析五、混合索引:现代数据库的融合趋势单一索引难以满足复杂需求,新一代数据库开始融合多种结构:TiDB:底层使用 LSM 树(RocksDB),上层构建分布式 B+ 树索引MongoDB:默认 B 树索引,同时支持全文检索(倒排)和地理空间索引ClickHouse:主键使用稀疏索引(类似 B+ 树),配合 MergeTree 引擎优化分析查询此外,向量索引(如 HNSW、IVF)正随着 AI 应用兴起,用于相似性搜索,进一步拓展索引的边界。六、结语索引是数据库性能的“命门”,其选择绝非技术炫技,而是对业务访问模式的深刻理解。B+ 树在事务型系统中屹立不倒,LSM 树在写密集场景大放异彩,倒排索引则撑起了整个搜索生态。作为工程师,我们不仅要知其然,更要知其所以然——理解每种索引背后的权衡(Trade-off):读写放大、空间效率、一致性模型。唯有如此,才能在面对海量数据、高并发、低延迟的挑战时,做出最合理的架构决策。未来,随着硬件演进(如持久内存、NVMe)与 AI 负载增长,索引技术将继续演化,但其核心使命始终不变:让数据触手可及。
-
全网同名「悬着的心终于死了」,全网同名认证~ 题主要批量修改网上下来的一些图片的后缀名,因为之前学艺不精,搞出来很多问题,这里记录一下(>_<)。之前学习操作文件的时候很草率,总结了一套文件基本操作流程:现在回来才发现这个套路并不适合所有的文件类型先上错误代码:import java.io.*;public class Main { public static void main(String[] args) throws Exception { String imgPath="D:\\文件夹/00015.webp"; File file=new File(imgPath); FileReader fileReader=new FileReader(file); FileWriter fileWriter=new FileWriter(file); String fatherPath=file.getParent(); String name=file.getName(); System.out.println(file.renameTo(new File(fatherPath,toJpg(name)))); try { fileReader.close(); fileWriter.close(); }catch (Exception e){ e.printStackTrace(); } } public static String toJpg(String oName){ int path=oName.lastIndexOf("."); return oName.substring(0,path)+".jpg"; }}操作的时候,各种错误:这里我总结为2个问题:1.FileWriter错误使用就像前面所说,我是直接套“模板”写,FileReader和FileWriter虽然没用到,但没报错就没删(这次长记性了>m<)经过排查,发现因为我多写这一句FileWriter才导致图片损坏这里是搜集到的结果如果 WebP 文件已经损坏,可以考虑使用专门的工具进行修复。例如,FabConvert 提供了一个免费的 WEBP 修复工具,可以在任何具有现代网络浏览器的系统上运行,并且没有使用限制未正确关闭流:在使用 FileWriter 写入数据后,如果没有正确关闭流,可能会导致数据未完全写入,从而损坏文件 。异常处理不当:如果在写入过程中发生异常,而异常没有被正确捕获和处理,可能会导致文件处于不一致的状态 。写入中途断电或系统崩溃:在写入过程中,如果遇到断电或系统崩溃等意外情况,可能会导致文件写入未完成,从而损坏文件。文件系统限制:某些文件系统可能有写入限制,例如最大文件大小或特定格式要求,不遵守这些限制可能会导致文件损坏。缓存问题:有时候浏览器可能会缓存旧版本的图片文件,导致新的 WebP 图片无法加载,这可能是文件损坏的一个表现 。文件损坏:检查 WebP 图片文件是否损坏或完整。有时候图片文件可能会在上传或保存过程中出现问题 。为了避免文件损坏,应该采取以下措施:确保在使用 FileWriter 后正确关闭它。使用异常处理来捕获并处理写入过程中可能发生的错误。避免在没有适当同步机制的情况下进行并发写入。使用事务或日志记录来确保写入操作的原子性和一致性。检查磁盘空间,并确保应用程序有足够的权限来写入文件。这里我并没有用FileWriter写入数据,所以应该是文件系统限制的原因,希望有懂的大佬可以在评论区解答一下(ˊ˘ˋ*)♡。2.FileReader错误使用这里通过反复测试发现,在没有FileReader这一句是file.renameTo()是可以执行的,原因是在使用FileReader时会资源锁定。以下为总结:在许多编程语言中,当你使用 FileReader 或类似的文件读取类与一个 File 对象关联后,你通常不能再直接操作这个 File 对象来读取文件。这主要是因为以下几个原因:资源锁定:一旦 FileReader 打开了一个文件,操作系统可能会锁定这个文件,防止其他进程或线程同时读取或写入,以避免数据损坏或冲突。状态管理:FileReader 可能内部维护了文件的状态信息,如当前读取位置。如果尝试用同一个 File 对象再次创建 FileReader,可能会遇到状态不一致的问题。设计模式:编程语言的设计可能鼓励使用流式操作,即一次只通过一个流(如 FileReader)来处理文件,而不是同时打开多个流。资源释放:如果 FileReader 没有被正确关闭,它可能会持续占用文件资源,导致其他操作无法进行。API限制:某些编程语言或库可能在API设计上限制了对同一个文件对象的多次使用,以简化资源管理和错误处理。如果需要在同一个程序中多次读取同一个文件,通常的做法是:在每次读取操作之后,确保关闭 FileReader 对象。如果需要再次读取,可以重新打开文件,创建一个新的 FileReader 对象。例如,在Java中,你可以这样做:File file = new File("path/to/your/file.txt");// 第一次读取FileReader reader1 = new FileReader(file);// ... 执行读取操作 ...reader1.close();// 第二次读取FileReader reader2 = new FileReader(file);// ... 执行读取操作 ...reader2.close();综上,修改了这两个问题后,修改文件后缀名就成功了import java.io.*;public class Main { public static void main(String[] args) throws Exception { String imgPath="D:\\文件夹/00015.webp"; File file=new File(imgPath); //FileReader fileReader=new FileReader(file); //FileWriter fileWriter=new FileWriter(file); String fatherPath=file.getParent(); String name=file.getName(); System.out.println(file.renameTo(new File(fatherPath,toJpg(name)))); } public static String toJpg(String oName){ int path=oName.lastIndexOf("."); return oName.substring(0,path)+".jpg"; }最后叨叨一句,自己准备的模板一定要完全弄懂口牙_(:зゝ∠)_
-
如图,跑JAVA代码时候发现JFrame这个图形化的类没办法使用
-
JFR(Java Flight Recorder)是 JDK 内置的高性能诊断工具,以极低开销记录 JVM 和应用运行时的关键事件。然而,不当配置可能导致录制开销升高、文件过大或分析困难。尤其在高并发、长时间运行的生产环境(如鲲鹏 ARM64 服务器)中,合理优化 JFR 的使用策略至关重要。本文将从 录制配置、资源控制、事件筛选、分析效率 四个维度,系统讲解如何优化 JFR 的性能表现。一、核心原则:平衡“数据完整性”与“运行开销”JFR 的默认配置(profile.jfc)已针对通用场景做了权衡,但实际业务需根据目标调整:目标推荐策略长期监控(7×24)仅启用关键事件(GC、线程、CPU 采样),降低采样频率故障复现启用详细事件(方法、异常、I/O),短时间高保真录制性能压测对比使用统一配置,确保数据可比性二、优化录制阶段的性能1. 选择合适的预设模板JDK 提供两个内置模板:default.jfc:基础事件(低开销,适合长期运行)profile.jfc:包含方法采样、锁竞争等(中等开销,适合性能分析)建议:# 长期监控用 default-XX:StartFlightRecording=settings=default,duration=1h,filename=/data/jfr/low.jfr# 深度分析用 profile-XX:StartFlightRecording=settings=profile,duration=5m,filename=/data/jfr/high.jfr 毕昇 JDK 还提供 kunpeng-optimized.jfc(如有),可进一步适配 ARM64。2. 自定义 .jfc 配置文件(推荐)复制并修改模板,关闭非必要事件:<!-- custom-low-overhead.jfc --><configuration ...> <event name="jdk.MethodSampling"> <setting name="enabled">false</setting> <!-- 关闭方法采样 --> </event> <event name="jdk.JavaMonitorEnter"> <setting name="enabled">true</setting> <setting name="threshold">10ms</setting> <!-- 仅记录 >10ms 的锁等待 --> </event> <event name="jdk.GCPhasePause"> <setting name="enabled">true</setting> </event></configuration>启动时指定:-XX:StartFlightRecording=settings=/path/to/custom-low-overhead.jfc,...3. 控制录制时长与文件大小避免无限制录制导致磁盘爆满:# 方式1:固定时长duration=10m# 方式2:循环录制(保留最近数据)maxsize=500MB,maxage=1h# 方式3:条件触发(JDK 17+ 支持)-XX:FlightRecorderOptions:repository=/tmp/jfr-cache生产建议:单文件 ≤ 1GB总录制时长 ≤ 30 分钟(除非明确需要长期趋势)4. 调整采样频率(降低 CPU 开销)关键参数:jdk.ThreadCPULoad:线程 CPU 采样间隔(默认 1s)jdk.ExecutionSample:方法栈采样间隔(默认 10ms)在自定义 .jfc 中调整:<event name="jdk.ExecutionSample"> <setting name="period">100ms</setting> <!-- 从 10ms 放宽到 100ms --></event> 注意:采样间隔越长,热点方法识别精度越低。三、减少 I/O 与内存开销1. 使用高速存储路径将 .jfr 文件写入 SSD 或内存盘:filename=/dev/shm/app.jfr # 写入 tmpfs(内存文件系统) 优势:避免磁盘 I/O 成为瓶颈 风险:重启丢失,需及时备份2. 启用压缩(JDK 17+)-XX:FlightRecorderOptions=compress=true可减少 30%~50% 文件体积。3. 避免多进程同时写同一目录每个 Java 进程应使用独立子目录,防止文件锁竞争。四、优化分析阶段的效率1. 使用命令行快速筛查(避免 GUI 开销)# 查看 GC 暂停总时间jfr print --events GCPhasePause app.jfr | grep "duration"# 统计最耗时的 10 个方法jfr summary app.jfr --category "Code" | head -n 102. 在分析机而非生产机运行 JMC将 .jfr 文件拷贝至开发机或专用分析服务器;避免在生产环境启动图形界面工具。3. 使用脚本自动化分析结合 jfr 命令 + Shell/Python 脚本,实现:自动提取关键指标生成性能报告触发告警(如 GC 暂停 > 100ms)示例脚本片段:MAX_PAUSE=$(jfr print --events GCPhasePause app.jfr | awk '/duration/ {print $2}' | sort -nr | head -1)if [ "$MAX_PAUSE" -gt 100000000 ]; then # 100ms in nanoseconds echo "ALERT: Max GC pause exceeds 100ms!"fi五、鲲鹏 ARM64 环境下的特别建议优先使用毕昇 JDK其 JFR 实现针对鲲鹏处理器的缓存、NUMA 架构优化,事件采集效率更高。关注 ARM64 特有事件如:jdk.CPULoad:ARM64 大小核调度可能影响 CPU 利用率jdk.NativeLibrary:验证 native 库是否为 ARM64 编译对比 x86 基线在相同负载下,分别录制 x86 与鲲鹏的 JFR 数据,使用 JMC 的 Compare 功能 定位架构差异点。
-
什么是 JFR?Java Flight Recorder(JFR)是 Oracle 自 JDK 7 引入、并在 JDK 11+ 开源的高性能事件记录框架。它以极低的性能开销(通常 <1%)持续收集 JVM 和应用层的关键事件,包括:GC 活动(暂停时间、堆变化)线程状态(阻塞、锁竞争)方法采样(CPU 热点)I/O 操作异常抛出类加载JIT 编译行为优势:内置于 JDK,无需额外依赖支持长时间录制(小时级)事件粒度细、上下文完整官方工具 JMC(Java Mission Control)提供可视化分析启用 JFR 并录制数据方法 1:启动时开启录制(推荐用于生产)java -XX:+FlightRecorder \ -XX:StartFlightRecording=duration=300s,filename=/tmp/app.jfr,name=MyAppProfile \ -jar your-app.jar参数说明:duration=300s:录制 5 分钟后自动停止filename:输出文件路径name:录制会话名称方法 2:运行时动态开启(需 JDK 14+ 或使用 jcmd)# 查看 Java 进程 PIDjps# 启动 60 秒录制jcmd <PID> JFR.start duration=60s filename=/tmp/runtime.jfr# 手动停止(可选)jcmd <PID> JFR.stop name=1注意:鲲鹏服务器上建议将 .jfr 文件保存至高速 SSD,避免 I/O 影响录制性能。 分析 JFR 数据工具 1:Java Mission Control(JMC)下载 JMC(支持 ARM64):华为毕昇 JDK 自带 JMC或从 Adoptium 下载 ARM64 版打开 .jfr 文件:jmc /tmp/app.jfr关键分析视图:Memory → Garbage Collections:GC 频率与暂停时间Threads → Thread Dump:线程阻塞与锁竞争Code → Hot Methods:CPU 消耗最高的方法I/O → File Read/Write:磁盘 I/O 瓶颈工具 2:命令行快速查看(无需 GUI)# 使用 jfr 工具(JDK 19+ 内置)jfr print --events GCPhasePause /tmp/app.jfr# 统计方法采样jfr summary /tmp/app.jfr 在鲲鹏服务器上,JFR 是 Java 应用性能调优不可或缺的工具。结合毕昇 JDK 的 ARM64 优化,可以:精准识别 GC、锁、I/O 等瓶颈;对比 x86 与 ARM64 的性能差异;验证 JVM 参数调优效果;构建性能基线,支撑容量规划。
-
鸿蒙版CodeArts IDE,进行Java开发spring boot微服务,支持svn和maven配置吗?如果不支持,有没有其他的解决办法,如果有的话,有详细步骤吗?第一次用CodeArts IDE,还不知道怎么用呢
-
在 Spring Boot 开发中,数组(Array)作为一种基础而高效的数据结构,广泛应用于参数接收、配置管理、批量处理等场景。尽管 Java 生态中更常使用 List、Set 等集合类型,但在特定场合(如性能敏感、固定长度、与前端/接口协议对齐等),原生数组仍有不可替代的价值。一、控制器中接收数组参数1.1 接收 URL 查询参数中的数组前端可通过重复参数名传递数组,Spring Boot 自动绑定为 Java 数组或 List。@RestControllerpublic class UserController { // 方式1:使用数组接收 @GetMapping("/users") public String getUsersByIds(@RequestParam("ids") Long[] ids) { return "Received IDs: " + Arrays.toString(ids); } // 方式2:使用 List 接收(推荐) @GetMapping("/users/list") public String getUsersByIdsList(@RequestParam("ids") List<Long> ids) { return "Received IDs: " + ids; }}请求示例:GET /users?ids=1&ids=2&ids=3 支持:Long[]、String[]、int[] 等基本类型及包装类数组 注意:若参数为空(如 /users),ids 将为 null,建议加 required = false 或设默认值@RequestParam(value = "ids", required = false, defaultValue = "") String[] ids1.2 接收 JSON 请求体中的数组当请求体为 JSON 格式时,可直接映射到对象字段中的数组。public class BatchUpdateRequest { private Long[] userIds; // 或 List<Long> userIds private String status; // getter/setter}@PostMapping("/batch-update")public ResponseEntity<String> batchUpdate(@RequestBody BatchUpdateRequest request) { System.out.println("User IDs: " + Arrays.toString(request.getUserIds())); return ResponseEntity.ok("Success");}请求体示例:{ "userIds": [101, 102, 103], "status": "ACTIVE"}Jackson 默认支持数组反序列化若需自定义行为,可配置 ObjectMapper二、在配置文件中使用数组2.1 application.yml 中定义数组app: supported-languages: [zh-CN, en-US, ja-JP] max-retry-attempts: 3 admin-ids: - 1001 - 1002 - 10032.2 使用 @ConfigurationProperties 绑定@Component@ConfigurationProperties(prefix = "app")@Data // Lombok 注解,自动生成 getter/setterpublic class AppProperties { private String[] supportedLanguages; private int maxRetryAttempts; private Long[] adminIds;} 支持 String[]、int[]、Long[] 等 不支持泛型数组(如 List<String> 需用 List)2.3 使用 @Value 注入数组(不推荐复杂场景)@Value("${app.supported-languages}")private String[] supportedLanguages;// 或使用 SpEL 表达式(适用于简单字符串数组)@Value("#{'${app.admin-ids}'.split(',')}")private Long[] adminIds; // 需确保配置为逗号分隔 缺点:类型转换弱、错误提示不友好,建议优先使用 @ConfigurationProperties三、数组与 JSON 序列化Spring Boot 默认使用 Jackson 处理 JSON。3.1 返回数组作为响应体@GetMapping("/languages")public String[] getSupportedLanguages() { return new String[]{"zh-CN", "en-US", "ja-JP"};}响应结果:["zh-CN","en-US","ja-JP"]3.2 自定义序列化行为(可选)若需控制空数组、null 值等行为,可在字段或类上添加注解:public class ApiResponse { @JsonInclude(JsonInclude.Include.NON_EMPTY) private String[] tags = {}; // 空数组不返回 // getter/setter}
-
一、基础概念进程 我们知道CPU是主机上的中央核心处理器,CPU的核数代表着主机能在一个瞬间同时并行处理的任务数,单核CPU只能在内存中并发处理任务。而在现有的操作系统中,几乎都支持进程这个概念。进程是程序的在内存中的一次执行过程,具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。线程 线程在程序中是独立的、并发的执行流,与分隔的进程相比隔离性会更小,线程之间共享内存、文件句柄和其它的进程应有的状态。线程比进程具有更高的性能,这是由于同一进程中的线程具有共性。简单理解,多线程是进程中并行执行的多个子程序。并发性和并行的区别 并行是指在同一时刻,有多条指令在多个处理器上同时执行;而并发是指在同一时刻只能执行,但是通过多进程快速轮换执行可以达到同时执行的效果。CPU主频就代表着这些进程之间频繁切换的速度。二、创建线程的三种方式2.1 通过继承Thread类来启用Java语言中JVM允许程序运行多个线程并通过java.lang.Thread类来实现。Thread类的特性 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体,并通过该Thread对象的start()方法来启动线程。流程:定义子类继承Thread类;子类中重写Thread类中的run方法;创建Thread子类对象,即创建了线程对象;调用线程对象start方法:启动线程,调用run方法。 具体代码示例首先构建一个继承Thread类的子类//继承Thread类的方式实现多线程public class TestThread extends Thread{ @Override public void run(){ System.out.println("多线程运行的代码"); }}AI写代码java运行 调用线程public class Test{ public static void main(String[]args){ Thread t = new TestThread(); t.start(); //启动线程 }}AI写代码java运行2.2 实现Runnable接口来实现流程定义子类,实现Runnable接口。子类中重写Runnable接口中的run方法。通过Thread类含参构造器创建线程对象。将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中。调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。 实现Runnable接口public class TestRunnable implements Runnable{ @Override public void run(){ System.out.println("实现Runnable接口运行多线程"); }}AI写代码java运行实现多线程public class Test{ public static void main(String[]args){ Thread t = new Thread(new TestRunnable); //带有线程名称的实例化线程对象。可以通过Thread.currentThread().getName()获取 //Thread t = new Thread(new TestRunnable,"the FirstThread"); t.start(); //启动线程 }}AI写代码java运行与继承Thread类的区别继承Thread:线程代码存放Thread子类run方法中。重写run方法实现Runnable:线程代码存在接口的子类的run方法。实现run方法 实现Runnable接口方法的好处 实现Runnable接口方法通过继承Runnable接口避免了当继承的局限性,同时也使得多个线程可以同时共享一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。 2.3 实现Callable接口 在前面通过实现Runnable接口创建多线程时,Thread类的作用就是把run方法包装成线程的执行体。而从Java5以后,Java提供了一个Callable接口中的call()方法作为线程执行体,同时call()方法可以有返回值,也可以抛出异常。public class Test{ public static void main(String[]args){ //创建callable对象 ThirdThread tt = new ThirdThread(); //使用FutureTask来包装Callable对象 FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{ ... ... }); new Thread(task,"有返回值的线程").start(); try{ //获取线程返回值 System.out.println("子线程的返回值" + task.get()); }catch (EXception ex){ ex.printStackTrace(); } }}AI写代码java运行Callable接口实现类和Runnable接口实现类的区别在于是否有参数返回! 三、Thread类的相关方法常用方法如下:void start():启动线程,并执行对象的run(0方法run():线程在被调度时执行的操作String getName():返回线程的名称void setName(String name):设置该线程名称static currentThread():返回当前线程 public class Test{ public static void main(String[]args){ TestRun r1 = new TestRun(); Thread t1 = new Thread(r1); //为线程设置名称 t1.setName("线程t1"); t1.start(); //启动线程 System.out.println(t1.getName()); //若没指定,系统默认给出的线程名称是Thread-0.... }}public class TestRun implements Runnable{ @Override public void run(){ System.out.println("实现Runnable接口运行多线程"); }}AI写代码java运行线程优先级线程的优先级设置增加了线程的执行顺序靠前的概率,是用一个数组1-10来表示的,默认的优先级是5。涉及的方法有:getPriority()和setPriority()//获取优先级t1.getPriority();//设置优先级t1.setPriority(10);AI写代码java运行线程让步static void yield()线程让步,即暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,则跳过。Thread.yield();AI写代码java运行线程阻塞join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止。try{ //获取线程返回值 t1.join();}catch (EXception ex){ ex.printStackTrace(); }AI写代码java运行线程睡眠try{ Thread.sleep(1000);//当前线程睡眠1000毫秒}catch(InterruptedException e)( e.printStackTrace();}AI写代码java运行线程生命结束t1.stop();AI写代码java运行判断当前线程是否存活t1.isAlive();AI写代码java运行四、生命周期线程从创建、启动到死亡经历了一个完整的生命周期,在线程的生命周期中一般要经历五种状态:新建——就绪——运行——阻塞——死亡。新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态;就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,也就是在执行.start()方法后;运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能,此时run()方法的代码开始执行;阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态;死亡:线程完成了它的全部工作或线程被提前强制性地中止 。 线程可能以如下三种方法结束:run或call方法执行完成后线程抛出一个未捕获的Exception或Error直接调用了stop()方法五、同步锁和死锁5.1 同步锁 多线程模式的提出势必就会带来线程同步的问题,在保证数据一致性上,我们需要为线程加上同步锁。Java中对于多线程安全的问题提出了同步机制,即在方法声明的时候加入synchronized关键字来修饰或者直接使用synchronized来锁一个demo5.1.1 synchronized加锁的两种方式 synchronized同步锁关键字修饰 //使用synchronized同步锁关键字修饰需要同步执行的方法体public synchronized void drawing(int money){ 需要同步执行的代码}AI写代码java运行注意: 在普通方法上加同步锁synchronized,锁的是整个对象,不是某一个方法。如果是不同对象的话那么就是不同的锁。静态的方法加synchronized对于所有的对象都是同一个锁!synchronized锁一段demo使用这种方法来锁指向this的代码块使用的都是同一个同步锁。如果改成方法对象的话比如Account对象的话就是不同的同步锁。synchronized(this){ //表示当前的对象的代码块被加了synchronized同步锁 demo...}AI写代码java运行5.1.2 Lock 相比于上面的synchronized相应的锁操作,Lock提供了更为广泛的锁操作。其中包括ReadWriteLock(读写锁)和ReentrantLock(可重入锁),ReadWriteLock提供了ReentrantReadWriteLock的实现类。在Java8中引入了一个新的StampedLock类替代了传统的ReentrantReadWriteLock并给出了三种锁模式:Write、ReadOptimistic和Reading。ReentrantLock 实现demo class x{ //定义锁对象 private final ReentrantLock lock = new ReentrantLock(); //... //定义需要保证线程安全的方法 public void m(){ lock.lock(); try{ //需要保证线程安全的demo } finally{ lock.unlock(); } }}AI写代码java运行5.2 死锁不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。解决方法专门的算法、原则,比如加锁顺序一致尽量减少同步资源的定义,尽量避免锁未释放的场景六、线程通信 当我们手动开启并在控制台中输出两个线程的运行过程的时候,程序并不能每次都准确的控制两个线程的轮换执行的先后次序,所以Java中也提供了一些机制来保证线程的协调运行。在传统的Java中,基于同步锁synchronized关键字提供了借助于Object类的wait()、notify()和notifyAll()方法来控制线程的阻塞情况,而之后也出现了基于Condition和阻塞队列BlockingQueue来控制线程阻塞的情况。6.1 传统的线程通信Object类中提供的wait()、notify()和notifyAll()方法必须由一个同步监视器对象来调用,所以这三种方法必须基于同步锁synchronized关键字。wait():该方法会导致当前线程进入等待状态,直到其它的线程调用notify()或notifyAll()方法来唤醒该线程,wait方法有三种形式:不带时间参数(等待唤醒)、带毫秒时间参数(时间到自动唤醒)和带毫微秒的时间参数(时间到自动唤醒)。调用wait方法当前线程会释放对同步监视器的锁定。notify():唤醒该同步监视器上等待的单个线程,这种选择是按照优先级最高的来唤醒结束其等待状态。notifyAll():唤醒等待的所有线程。//使用时直接调用方法就行,但必须是在有synchronized修饰的方法内去调用才可wait();notify();notifyAll();AI写代码6.2 使用Condition来控制线程通信 对于程序不使用synchronized关键字来保证同步锁,而是采用Lock对象来保证同步,Java中提供了Condition类来保证线程通信。Contidion类中提供了类似于synchronized关键字中的三种方法:await()、signal()和signalAll(),替代了同步监视器的功能。await():类似于wait方法,会使得当前线程进入等待状态,直到其它线程调用signal()或signalAll()来唤醒。signal():唤醒单个线程。signalAll():唤醒多个线程。//显示定义Lock对象Lock lock = new ReentrantLock();//获取ConditionCondition cond = lock.newCondition();//需要同步的方法中加锁public void fun(){ //加锁过程 lock.lock(); try{ if(条件) cond.await(); //线程进入等待 else{ //唤醒其他线程 cond.signalAll(); } }catch(InterruptedException e){ e.printStrackTrace(); }finally{ //锁的释放 lock.unlock(); }}AI写代码java运行6.3 使用阻塞队列来控制线程通信 除了上述两种方法,Java5中还提供了BlockingQueue接口来作为线程同步的工具。它的工作原理是这样滴:当生产者往BlockingQueue接口中放入元素直至接口队列满了,线程阻塞;消费者从BlockingQueue接口队列中取元素直至队列空了,线程阻塞。BlockingQueue接口继承了Queue接口并提供了如下三组方法。 在队列尾部添加元素:add(E e)、offer(E e)、put(E e),当队列已满的时候,这三个方法分别会抛出异常、返回false和阻塞线程。在队列头部删除并返回删除元素:remove()、poll()、take()方法。当该队列已空时,这三个方法分别会抛出异常、返回false和阻塞线程。在队列头部取出但不删除元素:element()和peek(),当该队列已空时,分别会抛出异常和返回false在Java7之后,阻塞队列出现了新增,分别是:ArrayBlockingQueue、LinkedBlockingQueue、priorityBlockingQueue、SynchornizedQueue和DelayQueue这五个类。 七、线程池 系统启动一个新线程的成本是比较高的,尤其是当系统本身已经有大量的并发线程时,会导致系统性能急剧下降,甚至会导致JVM崩溃,因此我们通常采用线程池来维护系统的并发线程。与数据库连接池类似的时,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动后一个空闲的线程来执行它们的run()或call()方法,当运行结束后,该线程不会死亡而是返回线程池中进入空闲等待状态。 ExecutorService代表尽快执行线程的线程池,程序只需要将一个Runnable对象或Callable对象传给线程池,就会尽快执行线程任务;ScheduledExecutorService代表可在指定延迟后或周期性地执行线程任务的线程池。7.1 ExecutorService类使用示例使用线程池的步骤如下:调用Executors类的静态工厂方法调用创建一个ExecutorService对象,该对象就代表着一个线程池;创建Runnable实现类或Callable实现类的实例,作为线程执行的任务;调用ExecutorService对象的submit()方法来提交Runnable或者Callable对象实例;结束任务时,调用ExecutorService对象的shutdown()方法来关闭线程池;//开启6个线程的线程池ExecutorService pool = Executors.newFixedThreadPool(6);//创建Runnable实现类Runnable target = ()->{...} //提交线程任务到线程池pool.submit(); //关闭线程pool.shutdown();AI写代码java运行 用完一个线程池后,应该调用该线程池的shutdown()方法,该方法将启动线程池的关闭序列并不再接受新任务,线程池中的任务依次执行完毕后线程死亡;或者调用线程池的shutdownNow()方法来直接停止所有正在执行的活动任务。7.2 Java8中的ForkJoinPool 计算机发展到现在其实基本的硬件都支持多核CPU,为了更好地利用硬件设备的资源,Java中提供了一个ForkJoinPool来支持将一个任务拆分成多个小任务并行计算。ForkJoinPool是ExecutorService的实现类,是一个特殊的线程池。构造器的两种方法ForkJoinPool(int num):创建一个包含num个并行线程的ForkJoinPool;ForkJoinPool():以Runtime.availableProcessors()方法的返回值作为parallelism参数(上面我写成了num)来创建改线程池实现通用池的两个静态方法ForkJoinPool commonPool():改方法返回一个通用池,通用池的状态不会受到shutdown()等方法的影响,System.exit(0)除外。int getCommonPoolParallelism():该方法返回通用池的并行级别注意: ForkJoinPool.submit(ForkJoinTask task) ,其中ForkJoinTask代表着一个可以并行和合并的任务,他有两个抽象的子类:RecursiveAction和RecursiveTask,分别代表着有返回值和无返回值的任务。class PrintTask extends RecursiveAction{ ... @Override protected void compute(){ ...... //分割任务 PrintTask t1 = new PrintTask(start,middle); PrintTask t2 = new PrintTask(middle,end); //并行执行子任务 t1.fork(); t2.fork(); }} public class Test{ public static void main(String[]args) throws Exception{ //实例化通用池对象 ForkJoinPool pool = new ForkJoinPool(); pool.submit(new PrintTask(0,1000)); //线程等待完成 pool.awaitTermination(2,TimeUnit.SECONDS); //关闭线程池 pool.shutdown(); }}AI写代码java运行总结 现有的所有企业都采用的是多线程并发的方式来开发的,也要求我们能够应对在高并发场景下保证系统服务的高可用的要求,所以多线程和异步编程我们必须牢牢掌握。这几章可能会比较枯燥,难度也会比较大,荔枝也是啃了一段时间嘿嘿嘿,在学这部分之前一定要把面向对象学好,要不然会晕哈哈哈~~~今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~————————————————版权声明:本文为CSDN博主「荔枝当大佬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_62706049/article/details/131848628
-
【问题来源】 内部测试环境功能测试 【问题简要】 麻烦咨询下,产生通话详单记录之后,会分别向tbilllog1-12和trecordinfo1-12表中写入数据,有种数据场景在tbilllog表中存在多条的情况,只取deveicetype=2(业务代表)的数据是能和trecordinfo表中的数据条数对应起来,但是只通过callid关联的话无法准确匹配到对应的录音数据,我在论坛上查看之前的问题求助,有看到回复过类似的问题,如下面的截图1,如果能通过callid和tbilllog表的callbegin和trecordinfo表的begintime一起关联能准确匹配到同一条数据,但是实际去查表发现有些数据的时间不是完全一样的,一条数据一样,另一条数据一个是14:07:23,另外一个是14:07:22,相差了1秒,如下面的截图2,所以想问下,这两个时间存在有些不一样的是否是写入过慢情况导致?那是否就无法通过tbilllog表的callbegin和trecordinfo表的begintime一起关联呢?【问题类别】 话单数据【AICC解决方案版本】 AICC 版本:AICC 25_300.0【期望解决时间】 尽快 【日志或错误截图】1、 2
-
如何安装SmartAssist Java 插件CodeArts IDE for Python版本: 3.4.1提交: c3be4d08ef4b68e19b23b4136f5f8a7a960d5095日期: 2025-07-15T04:00:00.368Z (3个月前)OS: Windows_NT x64 10.0.19045
-
【问题来源】 内部测试环境功能测试 【问题简要】 麻烦咨询下,媒体呼叫话单写入tbilllog1-12表中时效是实时写入吗?比如说通话挂断之后数据即可写入到对应月份的tbilllog表中,数据写入话单表这中间可能会存在延迟时间吗?【问题类别】 话单数据【AICC解决方案版本】 AICC 版本:AICC 25_300.0【期望解决时间】 尽快 【日志或错误截图】无
-
【问题来源】 内部测试环境功能测试 【问题简要】 在AICC产品文档_25.300.0版本的文档中查看AICC CC-CMS接口参考相关接口说明,没有找到有根据开始时间和结束时间作为查询条件对应的通话详单数据信息所对应的接口,截图上所标记的查询指定VDN下的呼叫信息接口有开始时间和结束时间,不过看下来返回的出参中有很多需要的字段不包括,比如保持时长,保持次数,通话状态等字段,咨询了解话单数据都记录在了tbilllog表中,目前有个场景对于客户在ivr挂断或排队挂断等数据需要做记录进行回呼以及这张表中没有相关保持时长等字段信息,对于我所说的这种场景目前最新的版本中是否有好的解决方案呢?谢谢!【问题类别】 CMS话单数据【AICC解决方案版本】 AICC 版本:AICC 25_300.0【期望解决时间】 尽快 【日志或错误截图】
-
篇讲了Lock锁、AQS相关的内容,本篇讲一下线程安全的类,拿来即用无需其他操作就能达到线程安全的效果,省力又省心 ~ ~你是否曾为多线程编程中的各种坑而头疼?本文将用生动比喻和实用代码,带你轻松掌握Java并发容器的精髓,让你的多线程程序既安全又高效!引言:为什么我们需要并发容器?想象一下传统的超市结账场景:只有一个收银台,所有人排成一队,效率低下。这就是传统集合在多线程环境下的写照。而现代并发容器就像拥有多个收银台的智能超市:多个收银台同时工作智能分配顾客到不同队列收银员之间互相协助在Java并发世界中,我们有三大法宝:ConcurrentHashMap - 智能分区的储物柜系统ConcurrentLinkedQueue - 无锁的快速通道阻塞队列 - 有协调员的等待区Fork/Join框架 - 团队协作的工作模式让我们一一探索它们的魔力!1. ConcurrentHashMap:智能分区的储物柜系统1.1 传统Map的问题:独木桥的困境// 传统HashMap在多线程环境下就像独木桥public class HashMapProblem { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); // 多个线程同时操作HashMap,就像多人同时过独木桥 // 结果:有人掉水里(数据丢失),桥塌了(死循环) }}1.2 ConcurrentHashMap的解决方案:多车道高速公路分段锁设计:把整个Map分成多个小区域,每个区域独立加锁ConcurrentHashMap架构: ├── 区域1 (锁1) → 储物柜组1 ├── 区域2 (锁2) → 储物柜组2 ├── 区域3 (锁3) → 储物柜组3 └── ...核心优势:写操作只锁住对应的区域,其他区域仍可读写读操作基本不需要加锁大大提高了并发性能1.3 实战示例:高性能缓存系统/** * 基于ConcurrentHashMap的高性能缓存 * 像智能储物柜系统,支持高并发存取 */public class HighPerformanceCache<K, V> { private final ConcurrentHashMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>(); // 获取或计算缓存值(线程安全且高效) public V getOrCompute(K key, Supplier<V> supplier) { return cache.computeIfAbsent(key, k -> new CacheEntry<>(supplier.get())).getValue(); } // 批量获取,利用并发特性 public Map<K, V> getAll(Set<K> keys) { Map<K, V> result = new HashMap<>(); keys.forEach(key -> { CacheEntry<V> entry = cache.get(key); if (entry != null && !entry.isExpired()) { result.put(key, entry.getValue()); } }); return result; }}2. ConcurrentLinkedQueue:无锁的快速通道2.1 无锁队列的魔法传统队列就像只有一个入口的隧道,所有车辆必须排队。而ConcurrentLinkedQueue就像多入口的立体交通枢纽:// 无锁队列的生动理解public class LockFreeQueueAnalogy { public void trafficHubComparison() { // 传统阻塞队列:单入口隧道,经常堵车 // ConcurrentLinkedQueue:立体交通枢纽,多入口同时通行 // 秘密武器:CAS(Compare-And-Swap)算法 }}2.2 CAS:优雅的竞争解决CAS就像礼貌的询问:public class PoliteInquiry { public void casAnalogy() { // 传统加锁:像抢座位,谁先坐到就是谁的 // CAS无锁:像礼貌询问"这个座位有人吗?" // 如果没人就坐下,有人就找下一个座位 }}2.3 实战示例:高并发任务处理器/** * 基于ConcurrentLinkedQueue的高性能任务处理器 * 像高效的快递分拣中心 */public class HighPerformanceTaskProcessor { private final ConcurrentLinkedQueue<Runnable> taskQueue = new ConcurrentLinkedQueue<>(); // 提交任务 - 无锁操作,极高吞吐量 public void submit(Runnable task) { taskQueue.offer(task); // 像快递放入分拣流水线 startWorkerIfNeeded(); } // 工作线程 - 无锁获取任务 private class Worker implements Runnable { public void run() { while (!Thread.currentThread().isInterrupted()) { Runnable task = taskQueue.poll(); // 像从流水线取快递 if (task != null) { task.run(); // 处理任务 } } } }}3. 阻塞队列:有协调员的等待区3.1 阻塞队列的四种行为模式想象餐厅的四种接待方式:public class RestaurantReception { public void fourBehaviors() { // 1. 抛出异常 - 霸道的服务员 // "没位置了!走开!" // 2. 返回特殊值 - 礼貌的前台 // "抱歉现在没位置,您要不等会儿?" // 3. 一直阻塞 - 耐心的门童 // "请您在这稍等,有位置我马上叫您" // 4. 超时退出 - 体贴的经理 // "请您等待10分钟,如果还没位置我帮您安排其他餐厅" }}3.2 七种阻塞队列:不同的餐厅风格Java提供了7种阻塞队列,每种都有独特的"经营理念":ArrayBlockingQueue:传统固定座位餐厅// 有10个桌位的餐厅,公平模式ArrayBlockingQueue<String> restaurant = new ArrayBlockingQueue<>(10, true);LinkedBlockingQueue:可扩展的连锁餐厅// 最大容纳1000人的餐厅LinkedBlockingQueue<Order> orderQueue = new LinkedBlockingQueue<>(1000);PriorityBlockingQueue:VIP贵宾厅// 按客户等级服务的贵宾厅PriorityBlockingQueue<Customer> vipLounge = new PriorityBlockingQueue<>();DelayQueue:延时电影院// 电影到点才能入场DelayQueue<MovieScreening> schedule = new DelayQueue<>();SynchronousQueue:一对一传球游戏// 不存储元素,每个put必须等待一个takeSynchronousQueue<String> ballChannel = new SynchronousQueue<>(true);3.3 实战示例:生产者-消费者模式/** * 生产者-消费者模式的完美实现 * 像工厂的装配流水线 */public class ProducerConsumerPattern { private final BlockingQueue<Item> assemblyLine; public ProducerConsumerPattern(int lineCapacity) { this.assemblyLine = new ArrayBlockingQueue<>(lineCapacity); } // 生产者:原材料入库 public void startProducers(int count) { for (int i = 0; i < count; i++) { new Thread(() -> { while (true) { Item item = produceItem(); assemblyLine.put(item); // 流水线满时等待 } }).start(); } } // 消费者:产品出库 public void startConsumers(int count) { for (int i = 0; i < count; i++) { new Thread(() -> { while (true) { Item item = assemblyLine.take(); // 流水线空时等待 consumeItem(item); } }).start(); } }}4. Fork/Join框架:团队协作的智慧4.1 分而治之的哲学Fork/Join框架的核心理念:大事化小,小事并行,结果汇总就像编写一本巨著:传统方式:一个人从头写到尾Fork/Join方式:分给多个作者同时写不同章节,最后汇总4.2 工作窃取算法:聪明的互助团队public class TeamWorkExample { public void workStealingInAction() { // 初始:4个工人,每人25个任务 // 工人A先完成自己的任务 // 工人B还有10个任务没完成 // 工作窃取:工人A从工人B的任务列表"偷"任务帮忙 // 结果:整体效率最大化,没有人闲着 }}4.3 实战示例:并行数组求和/** * 使用Fork/Join并行计算数组和 * 像团队协作完成大项目 */public class ParallelArraySum { static class SumTask extends RecursiveTask<Long> { private static final int THRESHOLD = 1000; // 阈值 private final long[] array; private final int start, end; public SumTask(long[] array, int start, int end) { this.array = array; this.start = start; this.end = end; } @Override protected Long compute() { // 如果任务足够小,直接计算 if (end - start <= THRESHOLD) { long sum = 0; for (int i = start; i < end; i++) sum += array[i]; return sum; } // 拆分成两个子任务 int mid = (start + end) / 2; SumTask leftTask = new SumTask(array, start, mid); SumTask rightTask = new SumTask(array, mid, end); // 并行执行:一个fork,一个当前线程执行 leftTask.fork(); long rightResult = rightTask.compute(); long leftResult = leftTask.join(); return leftResult + rightResult; } } public static void main(String[] args) { long[] array = new long[1000000]; Arrays.fill(array, 1L); // 100万个1 ForkJoinPool pool = new ForkJoinPool(); long result = pool.invoke(new SumTask(array, 0, array.length)); System.out.println("计算结果: " + result); // 输出: 1000000 }}5. 性能对比与选择指南5.1 不同场景的工具选择使用场景推荐工具理由高并发缓存ConcurrentHashMap分段锁,读多写少优化任务队列ConcurrentLinkedQueue无锁,高吞吐量资源池管理LinkedBlockingQueue阻塞操作,流量控制优先级处理PriorityBlockingQueue按优先级排序延时任务DelayQueue支持延时执行直接传递SynchronousQueue零存储,直接传递并行计算Fork/Join框架分治算法,工作窃取5.2 性能优化要点public class PerformanceTips { public void optimizationGuidelines() { // 1. 合理设置容量:避免频繁扩容或内存浪费 // 2. 选择合适的队列:根据业务特性选择 // 3. 避免过度同步:能用无锁就不用有锁 // 4. 注意异常处理:并发环境下的异常传播 // 5. 监控资源使用:避免内存泄漏和资源耗尽 }}6. 最佳实践总结6.1 设计原则解耦生产消费:生产者专注生产,消费者专注消费合理设置边界:防止资源耗尽,保证系统稳定性优雅处理异常:不能让一个线程的异常影响整个系统监控与调优:根据实际负载调整参数6.2 常见陷阱与规避public class CommonPitfalls { public void avoidTheseMistakes() { // ❌ 错误:在并发容器中执行耗时操作 // ✅ 正确:快速完成容器操作,复杂逻辑异步处理 // ❌ 错误:忽略容量边界导致内存溢出 // ✅ 正确:合理设置容量,使用有界队列 // ❌ 错误:依赖size()做业务判断 // ✅ 正确:使用专门的状态变量 // ❌ 错误:在Fork/Join任务中执行IO // ✅ 正确:Fork/Join只用于计算密集型任务 }}结语:掌握并发编程的艺术Java并发容器就像精心设计的交通系统,每种工具都在特定场景下发挥独特价值:ConcurrentHashMap:智能的多车道高速公路ConcurrentLinkedQueue:无锁的立体交通枢纽阻塞队列:有协调员的智能等待区Fork/Join框架:团队协作的分布式工作模式掌握这些工具,你就能构建出既安全又高效的并发程序,真正发挥多核硬件的威力。记住:合适的工具用在合适的场景,这才是并发编程的真谛。现在,拿起这些利器,开始构建你的高性能并发应用吧! 转载请注明原文链接:https://www.cnblogs.com/sun-10387834/p/19171977
上滑加载中
推荐直播
-
码道新技能,AI 新生产力——从自动视频生成到开源项目解析2026/04/08 周三 19:00-21:00
童得力-华为云开发者生态运营总监/何文强-无人机企业AI提效负责人
本次华为云码道 Skill 实战活动,聚焦两大 AI 开发场景:通过实战教学,带你打造 AI 编程自动生成视频 Skill,并实现对 GitHub 热门开源项目的智能知识抽取,手把手掌握 Skill 开发全流程,用 AI 提升研发效率与内容生产力。
回顾中 -
华为云码道:零代码股票智能决策平台全功能实战2026/04/18 周六 10:00-12:00
秦拳德-中软国际教育卓越研究院研究员、华为云金牌讲师、云原生技术专家
利用Tushare接口获取实时行情数据,采用Transformer算法进行时序预测与涨跌分析,并集成DeepSeek API提供智能解读。同时,项目深度结合华为云CodeArts(码道)的代码智能体能力,实现代码一键推送至云端代码仓库,建立起高效、可协作的团队开发新范式。开发者可快速上手,从零打造功能完整的个股筛选、智能分析与风险管控产品。
回顾中 -
华为云码道全新升级,多会话并行与多智能体协作2026/05/08 周五 19:00-21:00
王一男-华为云码道产品专家;张嘉冉-华为云码道工程师;胡琦-华为云HCDE;程诗杰-华为云HCDG
华为云码道4月份版本全新升级,此次直播深度解读4月份产品特性,通过“特性解读+实操演示+实战案例+设计创新”的组合,全方位展现码道在多会话并行与多智能体协作方面的能力,赋能开发者提升效率
正在直播
热门标签