仓颉标准库 collection 并发修改异常 ConcurrentModificationException 完全指南
编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载导读本文基于仓颉Cangjie标准库std.collection中异常类的官方 API 文档系统讲解ConcurrentModificationException并发修改异常的定义、触发机制、使用方式与底层实现原理。读者将掌握该异常在什么场景下抛出、如何在遍历容器时规避它、如何显式创建与捕获它并能结合 concurrent_modification_exception.cj 等源码理解其版本号version/modCount校验的 fail-fast 设计。关联文档stdlib/doc/libs/std/collection/collection_package_api/collection_package_exception.md异常类总览定义与继承关系ConcurrentModificationException是仓颉标准库 collection 包中用于报告并发修改问题的异常类其完整声明如下见 concurrent_modification_exception.cjpublic class ConcurrentModificationException : Exception { public init() public init(message: String) }核心要点继承体系该类直接继承自标准库的Exception基类属于受检查的异常体系关于Exception的完整定义见 core 包异常文档因此可以在try-catch中按类型捕获。类名自描述源码中通过protected override func getClassName(): String返回ConcurrentModificationException保证了toString()/message输出与类名一致。适用范围collection 包提供的容器类如ArrayList、HashMap、TreeMap、ArrayDeque、ArrayStack、LinkedList等均不支持并发修改。当函数检测到不同步的并发修改时就会抛出该异常。术语说明这里的并发修改并不仅指多线程并发。在同一线程内先创建迭代器/视图、再于遍历过程中修改容器同样属于不同步的修改会触发该异常。其本质是结构修改发生在迭代进行中。典型的抛出异常场景文档明确了两类最常见的触发场景下面结合源码逐一展开。场景一for-in 遍历过程中修改容器在for-in遍历一个容器的过程中对该容器进行修改删除、插入、清空等结构性修改时绝大多数迭代器会抛出ConcurrentModificationException。唯一的例外是HashMapIterator的remove()方法——它是被官方允许的遍历中删除接口。以 hash_map.cj 中的HashMapIterator为例其next()方法在每次取元素前都会执行版本校验Frozen public func next(): ?(K, V) { if (lockVersion ! data.version()) { throw ConcurrentModificationException() } // ... 跳过空槽位并返回下一个键值对 }ArrayListIterator也有同样的校验逻辑见 array_list.cjFrozen OverflowWrapping public func next(): OptionT { if (myData.myVersion ! initVersion) { throw ConcurrentModificationException() } // ... }TreeMap的两个方向迭代器同样通过公共的checkVersion()函数统一校验见 tree_map.cjfunc checkVersionK, V(node: TreeMapEntryViewK, V) where K : ComparableK { if (node.lockVersion ! node.map.version()) { throw ConcurrentModificationException() } }fail-fast 机制的实现原理以ArrayList为例迭代器构造时把容器当前的版本号保存为initVersion容器每次发生结构性修改add、remove、clear等都会执行myVersion见 array_list.cj 等多处。当迭代器next()发现myData.myVersion ! initVersion时说明容器在迭代期间被修改过立即抛出ConcurrentModificationException从而避免在脏数据上继续迭代产生不可预期的结果。HashMap则使用modCount计数并通过version()方法暴露见 hash_map.cj。典型错误代码会抛异常import std.collection.* main(): Unit { let map HashMapString, Int() map[a] 1 map[b] 2 // 遍历过程中执行修改操作抛出 ConcurrentModificationException try { for ((k, v) in map) { if (k a) { map.remove(a) // 结构性修改 } } } catch (e: ConcurrentModificationException) { println(捕获到并发修改异常: ${e.message}) } }唯一例外——HashMapIterator.remove()HashMapIterator的remove()方法在删除后主动重新同步版本号lockVersion data.version()因此不会抛出该异常见 hash_map.cj。也就是说如果确实需要在遍历HashMap时删除元素应显式使用迭代器的remove()而不是容器自身的remove()。场景二生命周期较短的类型视图类当使用生命周期较短的视图类型如MapEntryView其HashMap实现为HashMapEntryView见 hash_map.cj时只要其所在的容器内容被修改视图的后续访问就会抛出该异常。以HashMapEntryView为例它保存了创建时的容器版本lockVersion其getKey()、getValue()、setValue()三个核心方法均先做版本校验Frozen func getValue(): ?V { if (lockVersion ! map.version()) { throw ConcurrentModificationException() } return if (_isAbsent) { None } else { map.entries[index].value } }TreeMapEntryView也采用相同的校验模式见 tree_map.cj例如在next()遍历时先调用checkVersion(curNode)检查视图所依赖的容器版本是否变化。典型错误代码会抛异常import std.collection.* main(): Unit { let map HashMapString, Int() map[key] 1 let view map.entries[0] // 获取一个 MapEntryView视图 map[another] 2 // 修改容器内容 try { println(view.key) // 视图访问触发版本校验失败 } catch (e: ConcurrentModificationException) { println(捕获到并发修改异常) } }实践建议视图类EntryView / Iterator / SubList 等应当即取即用在获取后不要再去修改原容器若确实需要边遍历边增删优先使用迭代器提供的remove()等同步方法。构造方式一init() —— 无参数构造public init()功能构造一个不带异常信息的ConcurrentModificationException实例。在源码中对应调用父类Exception的无参构造super()不设置 message。完整示例可直接复制运行import std.collection.* main(): Unit { // 使用无参构造函数创建ConcurrentModificationException实例 let exception ConcurrentModificationException() println(创建了无参的ConcurrentModificationException实例) // 可以抛出并捕获该异常 try { throw ConcurrentModificationException() } catch (e: ConcurrentModificationException) { println(捕获到ConcurrentModificationException异常) } }运行结果创建了无参的ConcurrentModificationException实例 捕获到ConcurrentModificationException异常构造方式二init(String) —— 带异常信息构造public init(message: String)功能根据异常信息构造异常实例底层调用super(message)将 message 透传给基类Exception。参数说明参数类型说明messageString异常信息可通过异常的message属性读取完整示例可直接复制运行import std.collection.* main(): Unit { // 使用带消息的构造函数创建ConcurrentModificationException实例 let message 并发修改异常在遍历过程中修改了容器 let exception ConcurrentModificationException(message) println(创建了带消息的ConcurrentModificationException实例: ${exception.message}) // 可以抛出并捕获该异常 try { throw ConcurrentModificationException(容器在遍历过程中被修改) } catch (e: ConcurrentModificationException) { println(捕获到异常: ${e.message}) } }运行结果创建了带消息的ConcurrentModificationException实例: 并发修改异常在遍历过程中修改了容器 捕获到异常: 容器在遍历过程中被修改异常抛出点与触发条件的源码级梳理从仓库源码可以统计出该异常在当前 collection 包中的实际抛出分布均为throw ConcurrentModificationException()的调用点容器类典型抛出位置触发条件array_list.cjArrayListIterator.next()L55、谓词修改检测L425等迭代期间容器myVersion变化谓词回调中执行了修改操作array_deque.cjArrayDequeIterator.next()L235迭代期间容器被结构性修改array_stack.cjArrayStackIterator.next()L215迭代期间容器被结构性修改linked_list.cj迭代器next()L792、谓词修改检测L252同上hash_map.cjHashMapIterator.next()L1352、remove()L1381、HashMapEntryView的getKey/getValue/setValueL268/L283/L303、谓词修改检测L675/L690等容器modCount与视图/迭代器锁定的版本不一致tree_map.cj正/反序遍历next()L1150/L1171、checkVersion()L1313、TreeMapIterator.next()L1467等同上值得注意的细节除迭代器与视图外部分谓词predicate型 API也会抛出该异常。例如HashMap的某些以函数为参数的操作会在执行期间检测谓词内部是否进行了修改操作一旦发现即抛出带说明信息的异常The predicate cannot contain a modify operation.见 hash_map.cj。这类异常同样以ConcurrentModificationException类型抛出捕获时按同一类型处理即可。常见问题与规避建议为什么会边遍历边删除报错因为迭代器保存的是创建时刻的容器版本号容器任何结构性修改都会使版本号变化next()据此判定迭代已失效。这不是随机错误而是 fail-fast 的有意设计——宁可立即失败也不返回不一致的结果。遍历中想删除怎么办使用迭代器自身的remove()当前支持HashMapIterator.remove()或者先收集需要删除的键遍历结束后再统一删除。视图对象还能用吗只要容器发生修改之前获取的 EntryView 就会失效并抛异常视图应即取即用不要长期持有。如何获得更友好的报错信息捕获后用e.message获取异常信息若需要自定义描述可在业务代码中主动throw ConcurrentModificationException(自定义描述)。小结ConcurrentModificationException是仓颉标准库 collection 包提供的 fail-fast 并发修改检测机制的统一出口无论是迭代器、EntryView 视图还是谓词回调场景只要容器在外部持有引用期间发生了结构性修改该异常就会在下次访问时被抛出。理解其背后版本号myVersion/modCount/version()快照对比的实现原理见 array_list.cj 与 hash_map.cj有助于编写健壮、可预期的容器遍历代码并在多线程或延迟处理场景中第一时间定位并发修改问题。延伸阅读更多 collection 包 API 细节可查阅 collection 包类文档 与 collection 包接口文档异常的基类定义见 core 包异常文档。赞分享编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载相关推荐仓颉编程语言 std.collection 并发修改异常 ConcurrentModificationException 解析仓颉编程语言 std.collection 并发修改异常 ConcurrentModificationException 解析 导读 在仓颉编程语言的标准库 s文档知识库教程OpenHarmony仓颉标准库 std.argopt 参数解析异常 ArgumentParseException 实战详解仓颉标准库 std.argopt 参数解析异常 ArgumentParseException 实战详解 本篇文章聚焦仓颉标准库 std.argopt 包中唯一的人工智能AI 技能文档开发工具仓颉标准库 argopt 解析结果结构体 ParsedArguments 完全指南仓颉标准库 argopt 解析结果结构体 ParsedArguments 完全指南 ParsedArguments 是仓颉编程语言标准库 std.argopt文档知识库教程OpenHarmony上一篇PhotoGIMP 完整安装指南免费补丁让 GIMP 3 拥有 Photoshop 的熟悉布局下一篇GoldenDict-ng 音频播放引擎FFmpeg 后端的演变与外部播放器配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考