首页 > 解决方案 > 访问同步属性后,两个线程可以同时访问同一个方法吗?

问题描述

我对代码中的某些情况有点困惑。

private var state by synchronized<PlayerState>(Stopped())

val asStarted get() = state as? Started
val asStopped get() = state as? Stopped

inner class Stopped : PlayerState {
    fun start(bpm: BPM = BPM(), volume: Int? = null) {
        state = Started(bpm, volume)
    }
}

想象一下两个线程正在尝试执行player.asStopped?.start(),我这里有问题吗?我需要输入这样的内容吗?

inner class Stopped : PlayerState {
    @Synchronized
    fun start(bpm: BPM = BPM(), volume: Int? = null) {
        if(state is Started) return
        state = Started(bpm, volume)
    }
}

帮助表示赞赏。

标签: javamultithreadingkotlinthread-safety

解决方案


你的担心是对的。

您的代码的第一个版本确实有一个竞争条件,因为两个线程都可以创建Started对象,然后设置它们。一次只能有一个线程执行 setter,但第二个线程可以在第一个线程离开后立即进入它。

这在这里可能不是问题。(synchronized委托仍将防止访问 的任何低级问题state,例如查看部分构造的值。看起来没有其他状态需要与您的state属性相关联,因此它不能进入​​不一致的状态.) 但是能够启动两次可能并不好!

认为您的代码的第二个版本没有任何线程问题,如所写。(如果它没有@Synchronized,那么它确实会有一个time-of-check-to-time-of-use竞争条件。)

但是,它有点令人困惑,因为它使用了两种独立的锁定机制(@Synchronized方法锁定Stopped对象,而by synchronized状态有自己的锁定),并且很难遵循这些锁定可能交互的方式,即使在这个非常简单的示例中也是如此。

我还怀疑它可能导致周围代码中的竞争条件。一旦开始,您的播放器可以再次停止吗?如果是这样,stop()方法将锁定什么 - 如果是Started对象,那么那是另一个锁定,我可以在那里看到一些竞争条件。

因此,我强烈建议将其重构为仅使用一个锁。

我不知道synchronized您的代码正在使用的委托。如果它具有某种测试和设置、比较和交换或类似功能,那么您可以使用它来做您需要的事情。否则,恐怕最好放弃委托并自己进行锁定。

不过,这确实引发了一个关于设计的更广泛的问题:为什么你的Stopped类有一个start()方法? 当然是玩家开始和停止,而状态只是反映吗?所以改变状态根本不是当前状态的操作。

解决该设计问题几乎肯定会使修复锁定变得更容易。


推荐阅读