我看到过一些代码,
// value is a member of this class, so smart cast
// to non nullable doesn't happen
if (value != null) return value!!
因为value是类的成员,所以线程不能在检查和返回之间将值更改为null吗?从而导致撞车?
下面是一个更完整的例子,有两种不同的方法。在线程安全性方面,一个比另一个好吗?
class Example {
private var cachedVariable: SomeClass? = null
fun getCachedVariable(): SomeClass {
// need to use !! because can't smart cast because
// mutable property. But cant this crash if some
// other thread sets cachedVariable to null after
// this thread does the check?
if (cachedVariable != null) return cachedVariable!!
cachedVariable = SomeClass()
return cachedVariable!!
}
}
或
class Example2 {
private var cachedVariable: SomeClass? = null
fun getCachedVariable(): SomeClass {
// Does this avoid the possible crash of Example 1
return cachedVariable ?: SomeClass().also {
cachedVariable = it
}
}
}
4条答案
按热度按时间pxy2qtax1#
你是对的,如果它是一个var,那么另一个线程可以在该行的中间修改它。这就是为什么这段代码只编译!!,因为编译器知道它不能确定值是否为null。这也关系到如何!!通常表示代码气味,你通常期望Kotlin中的空安全代码,但这个结构不是。这明确地意味着它可以崩溃。
rmbxnbpk2#
因为value是类的成员,所以线程不能在检查和返回之间将值更改为null吗?从而导致撞车?
这是正确的,这也是为什么编译器在这里不会智能转换为非空值。你可以通过使用局部变量来解决这个问题:
对于你的第二个例子,也不是伟大的海事组织。如果这个字段只初始化一次,最好的选择是使用
by lazy
:如果出于某种原因,你不能或不想使用
by lazy
,你应该阅读Double-checked locking,这是安全和高性能的方法。z9ju0rcb3#
其他答案很好地解释了为什么Kotlin在这种情况下需要
!!
nullAssert。但通常,当你像这样访问一个示例变量时,你想在一个时刻捕获它的值,然后对捕获的值进行null检查。其他线程可以随时修改示例变量的值。但是如果你的函数创建了一个带有
val
的局部变量,那么 * 没有人 *,甚至是函数本身,可以再次修改这个值,所以如果你检查val
是非空的,那么这个检查就保证永远是好的。现在,在您的例子中,您还修改了
cachedVariable
,所以如果这是要在多线程环境中运行的代码,您需要使用互斥锁或监视器正确地锁定它。Java和Kotlin使用synchronized
使这变得很容易,但你仍然必须这样做。iugsix8n4#
即使你的第二个例子是错误的,即使它不会崩溃。考虑以下操作序列
1.线程1进入
getCachedVariable
。cachedVariable
为null1.线程2进入
getCachedVariable
。cachedVariable
为null1.线程1创建
SomeClass()
并将其分配给cachedVariable
1.线程2创建
SomeClass()
并将其分配给cachedVariable
没有崩溃,但现在两个线程引用不同的对象,如果线程1对它的引用被删除,线程1的对象将被垃圾收集。
看起来你真正想要的是
lazy
对于
Example
的每个示例,即使多个线程尝试访问它,也最多计算一次,并且所有线程都将安全地获得相同的示例。