Kotlin的`?.let`是线程安全的吗?

11

Kotlin的?.let是否线程安全?

假设一个变量a可以在不同的线程中更改。使用a?.let { /* */ }是否线程安全?如果它等价于if (a != null) { block() },那么在if中它不为null,在block中它会变成null的情况是否可能发生?


我猜这个操作符要做到线程安全可能有些过头了。 - Valeriy Katkov
3
当代码块被执行时,变量 a 可能为 null,但参数 it 不可能为空。也就是说,这段代码等价于 val copy = a; if (copy != null) { block(copy) } - JB Nizet
@4ntoine 当 Kotlin 编译器将可空类型智能转换为非空类型时,您可以确信它确实是非空的。如果代码不是线程安全的,编译器会给出编译器错误(就像在执行 if (a != null) { a.someFunction() } 时一样)。 - marstran
编译器错误信息如下(如果 aInt? 类型):智能转换为 'Int' 是不可能的,因为 'a' 是一个可变属性,在此期间可能已经被更改 - marstran
1个回答

13

a?.let { block() }的确等价于 if (a != null) block()

这也意味着,如果a是一个可变变量,则:

  1. 如果a是可变变量,那么在空值检查之后,它可能会被重新分配,并在block()执行期间保持为null值;

  2. 所有与并发相关的影响都有权利,如果a在线程之间共享,则需要适当的同步来避免竞态条件,如果block()再次访问a

然而,由于let { ... }实际上将其接收者作为单个参数传递给它所采取的函数,因此它可以用于捕获a的值并在lambda中使用它,而不是在block()中再次访问该属性。例如:

a?.let { notNullA -> block(notNullA) }

// with implicit parameter `it`, this is equivalent to:
a?.let { block(it) }

在这里,作为参数传递到lambda的a的值保证与检查null时的值相同。但是,在block()中再次观察a可能会返回null或不同的值,并且观察给定实例的可变状态也应该被正确同步。


3
而后一种情况是线程安全的。(这是指let参数始终与?.检查到的值相同,因此永远不会为null。但是,如果a在此期间发生了更改,则不会反映出来。) - gidds
@gidds 非常有趣。有证据吗? - 4ntoine
1
我认为这是由于 ?. 运算符的工作方式:它从其左侧的表达式中获取值(这可能意味着调用 getter 或其他内容),检查它是否为空,如果不为空,则调用该值后面的方法。在这种情况下,let() 然后将该值作为参数传递给函数。因此,在任何时候都没有机会获取不同的值。当然,如果该值是可变对象,则其状态可以更改。但它必须是相同的对象。 - gidds
1
@AlexeyRomanov 我的陈述假设 block 是无参的,所以我会说它成立,但并不能完全解释 ?.let { ... } 的语义。感谢您的提醒! - hotkey
我最后一个问题是,“a != null检查是原子和同步的吗?”看起来好像不是。 - 4ntoine
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接