通过惰性 {} 导致生产中出现强制转换异常/哈希映射在分配后丢失值

问题描述 投票:0回答:1

我们有一个看起来有点像这个小例子的代码:

// this is a class that is injected into a view model, it is injected once, as a parameter
// it is used in multiple functions in the view model, some of them may happen one after another or even at once
class UseCaseProvider(repository: Repository) {
    val useCaseMap = HashMap<Class<*>, UseCase<*, *>>()
    private val clearFiltersUseCase by lazy { ClearFiltersUseCase(repository) }
    // maaaany more usecases made in the exact same way...
    
    private fun <INPUT, OUTPUT> register(useCase: UseCase<INPUT, OUTPUT>) {
        useCaseMap[useCase::class.java] = useCase
    }
    
    internal inline <reified T: UseCase<*, *>> get(): T {
        if(!useCaseMap.containsKey(T::class.java)){
            when(T::class) {
                ClearFiltersUseCase -> register(clearFiltersUseCase)
                // maaaany more similar lines
                else -> throw IllegalStateException("UseCase not registered")
            }
        }
        return useCaseMap[T::class.java] as T
    }
}
// example usage in the view model: 
// useCaseProvider.get<ClearFiltersUseCase>().invoke() 
// .invoke() is always suspend

我们得到一个类转换异常,null 被转换为 UseCase 类,你知道为什么会发生这种情况吗?

我们在另一个模块中有一个类似的用例提供程序,但它没有使用惰性和注册函数,而是在创建提供程序后创建实例并将实例保存在哈希图中,并且它不会崩溃,因此我怀疑某种惰性是有问题的在这里?

我接受任何想法(惰性 {} 的竞争条件、线程安全是我唯一的想法,但我无法重现崩溃)

android kotlin android-viewmodel use-case
1个回答
0
投票

@broot 是对的,但为了分享更多关于为什么我们刚刚分配的值为 null 的见解

(警告:可能不是 100% 正确,但它似乎解释了为什么这个提供程序崩溃,而另一个在创建提供程序时分配每个用例的提供程序却没有崩溃):

当一个线程写入 DYNAMIC SIZE 哈希映射,而另一个线程尝试从中读取时,如果哈希映射必须调整大小并重新哈希(在后台完成),则它似乎会创建一个大小为前一个映射的 2 倍的副本,并且填充键(使用 null 作为值)并开始重新散列值...如果线程在该确切时间读取,它将从之前没有 null 值的键中获取 null 作为值

解决方法:

  1. 创建 Provider 时初始化 hashMap 内的所有对象(无新写入 = 无需调整大小)
  2. 使用ConcurrentHashMap
  3. 以某种方式限制哈希图的大小(并从100%适合您的情况的初始大小开始)
  4. 正如我们所做的:完全删除哈希图:D
© www.soinside.com 2019 - 2024. All rights reserved.