You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用lazy{}引发类型转换异常及HashMap值丢失问题排查

问题根源分析与解决方案

核心问题

你的UseCaseProvider出现的null类型转换异常和HashMap值丢失,本质是**lazy的线程安全局限性**+HashMap非线程安全组合导致的并发竞态条件问题。

具体原因拆解

  1. lazy的同步不覆盖map操作的竞态
    Kotlin默认的lazy(LazyThreadSafetyMode.SYNCHRONIZED)仅保证UseCase实例只会被初始化一次,但无法保证get()方法中「检查map是否存在key → 初始化lazy实例 → 写入map」这一整套流程的原子性。
    当多个线程同时调用get<ClearFiltersUseCase>()时,可能出现:

    • 线程A通过containsKey检查(map中无对应key),进入分支准备初始化lazy实例;
    • 线程B同时通过containsKey检查(此时线程A还没完成map写入),也进入同一分支;
    • 虽然lazy保证了实例唯一,但两个线程同时执行register()(即HashMap.put()),而HashMap的put操作是非线程安全的,并发写入会导致内部哈希表结构损坏,后续读取时可能出现key存在但返回null的情况,最终触发强转异常。
  2. HashMap本身的非线程安全特性
    HashMap在并发场景下的扩容、写入操作会出现数据丢失、结构错乱等问题,即使是同一key的重复写入,也可能因为并发修改导致后续读取失败,返回null。

  3. 对比无崩溃的实现逻辑
    另一模块在Provider构造时直接实例化所有UseCase并写入map,这一过程是在DI注入的单线程流程中完成的,没有并发操作map的场景,因此不会出现结构损坏或值丢失问题。

修复方案

方案一:改用线程安全Map+原子化初始化(推荐)

替换HashMap为ConcurrentHashMap,并利用其computeIfAbsent方法实现原子化的检查-初始化-写入逻辑,彻底消除竞态条件:

class UseCaseProvider(repository: Repository) {
    // 替换为线程安全的ConcurrentHashMap
    private val useCaseMap = ConcurrentHashMap<Class<*>, UseCase<*, *>>()

    internal inline <reified T: UseCase<*, *>> get(): T {
        return useCaseMap.computeIfAbsent(T::class.java) {
            when(T::class) {
                ClearFiltersUseCase::class -> ClearFiltersUseCase(repository)
                // 其他UseCase的实例化逻辑
                else -> throw IllegalStateException("UseCase not registered")
            }
        } as T
    }
}

computeIfAbsent是原子操作,保证同一key只会被初始化一次,且ConcurrentHashMap的所有操作都是线程安全的,彻底避免并发问题。

方案二:保留lazy但添加全局锁

如果必须保留lazy初始化,可以在get()方法中添加同步锁,保证检查-注册流程的原子性,同时替换map为线程安全实现:

class UseCaseProvider(repository: Repository) {
    private val useCaseMap = ConcurrentHashMap<Class<*>, UseCase<*, *>>()
    private val clearFiltersUseCase by lazy { ClearFiltersUseCase(repository) }
    // 其他lazy声明...

    // 全局锁对象
    private val lock = Any()

    internal inline <reified T: UseCase<*, *>> get(): T {
        synchronized(lock) {
            return useCaseMap.computeIfAbsent(T::class.java) {
                when(T::class) {
                    ClearFiltersUseCase::class -> clearFiltersUseCase
                    // 其他lazy属性
                    else -> throw IllegalStateException("UseCase not registered")
                }
            } as T
        }
    }
}

内容的提问来源于stack exchange,提问作者JustSightseeing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:33:26