我们有一个看起来有点像这个小例子的代码:
// 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 类,你知道为什么会发生这种情况吗?
我们在另一个模块中有一个类似的用例提供程序,但它没有使用惰性和注册函数,而是在创建提供程序后创建实例并将实例保存在哈希图中,并且它不会崩溃,因此我怀疑某种惰性是有问题的在这里?
我接受任何想法(惰性 {} 的竞争条件、线程安全是我唯一的想法,但我无法重现崩溃)
@broot 是对的,但为了分享更多关于为什么我们刚刚分配的值为 null 的见解
(警告:可能不是 100% 正确,但它似乎解释了为什么这个提供程序崩溃,而另一个在创建提供程序时分配每个用例的提供程序却没有崩溃):
当一个线程写入 DYNAMIC SIZE 哈希映射,而另一个线程尝试从中读取时,如果哈希映射必须调整大小并重新哈希(在后台完成),则它似乎会创建一个大小为前一个映射的 2 倍的副本,并且填充键(使用 null 作为值)并开始重新散列值...如果线程在该确切时间读取,它将从之前没有 null 值的键中获取 null 作为值
解决方法: