Kotlin synchronized() 是否不锁定基本类型?

Dor*_*Ari 3 kotlin

class Notification(val context: Context, title: String, message: String) {
    private val channelID = "TestMessages"

    companion object ID {
        var s_notificationID = -1
    }

    init {
        var notificationID = -1
        synchronized(s_notificationID) {
            if (++s_notificationID == 0)
                createNotificationChannel()
            notificationID = s_notificationID
        }
Run Code Online (Sandbox Code Playgroud)

以上是从两个线程同时调用的。中的断点createNotificationChannel()清楚地表明有时s_notificationID等于 1。

但是,如果我更改 synchronized(s_notificationID)synchronized(ID) 那么它似乎锁定得很好。

synchronized() 是否不锁定基本类型?如果是这样,为什么要编译?

Sil*_*olo 5

查看生成的 JVM 字节码表明该ID示例看起来像

synchronized(ID) { ... }
Run Code Online (Sandbox Code Playgroud)

这是你所期望的。然而,这个s_notificationID例子看起来更像

synchronized(Integer.valueOf(s_notificationID)) { ... }
Run Code Online (Sandbox Code Playgroud)

在 Java 中,我们只能同步对象,而不能同步原语。Kotlin 基本上消除了这种区别,但看起来您已经找到了一个实现仍然渗透的地方。由于s_notificationIDintJVM 而言是一个(因此,不是对象)但synchronized需要一个对象,因此 Kotlin 足够“聪明”,可以Integer.valueOf按需包装值。对您来说不幸的是,这会产生非常不一致的结果,因为

此方法将始终缓存 -128 到 127(含)范围内的值,并且可能缓存此范围之外的其他值。

因此,对于较小的数字,这可以保证锁定内存中您无法控制的某些缓存对象。对于大型对象,它可能是一个新对象(因此总是未锁定),或者它可能会再次出现在您手中的缓存对象上。

这里的教训似乎是:不要同步原始类型。


bro*_*oot 5

Silvio Mayolo 解释了为什么在基元上同步不是一个好主意(实际上,我认为编译器应该对此发出警告)。但我相信这段代码还有另一个问题,可能是使您的synchronized块并行工作的主要问题。

问题是您替换了 的值s_notificationID。即使它是一个对象,而不是基元,您的同步块仍然会并行运行,因为每次调用都synchronized使用不同的对象。this这就是为什么在 Java 中我们通常在需要修改的字段上进行同步而不是同步。