哪些场景下Kotlin的toSet方法会抛出java.lang.IllegalArgumentException
异常触发原因说明
核心根因
该异常本质是多线程并发修改集合未完全加锁导致的,你当前仅在读取markersMap的位置加了synchronized锁,只要存在任意一处修改markersMap的代码没有加同一把锁,就会触发该问题。
mapCapacity返回负数的逻辑
Kotlin标准库中mapCapacity的实现逻辑非常简单:
internal fun mapCapacity(expectedSize: Int): Int = when { expectedSize < 3 -> expectedSize + 1 expectedSize < 1073741824 -> expectedSize + expectedSize / 3 else -> 2147483647 }
它本身不会主动返回负数,只有当传入的expectedSize参数本身是负数时,最终返回结果才会是负数。这里的expectedSize就是你调用toSet()时读取的markersMap.values.size。
size为负的原因
默认的mutableMapOf返回的是线程不安全的LinkedHashMap,它的size是普通int类型变量,没有任何并发可见性或原子性保障:
- 多线程同时修改
size时会出现原子性问题,比如两个线程同时执行减元素操作,指令交错后会出现计算下溢,最终size变成负数 - 你在持锁读取
size时,其他未持锁的线程正在修改size,也可能读到更新过程中的中间值,也就是负数
当size为负时,传入mapCapacity就会得到负的初始容量,传给LinkedHashMap构造函数就会抛出你遇到的IllegalArgumentException: Illegal initial capacity: -1异常。
修复方案
- 检查所有修改
markersMap的位置,包括put、remove、clear等操作,全部加上synchronized(markersMap)锁,保证所有读写操作都持有同一把锁,彻底避免并发修改 - 也可以直接替换为线程安全的
ConcurrentHashMap实现,无需手动加锁就能避免该问题:
private val markersMap = ConcurrentHashMap<Any, Marker>()
内容的提问来源于stack exchange,提问作者Florian Schrofner
相关产品推荐
相关产品推荐

