布尔变量在检查给定示例时是否需要同步?

Does boolean variable need synchronization while checking in the given example?

我正在探索 an example of a simple android game,我对其同步逻辑有疑问。

给定两个字段:

private boolean mRun = false;
private final Object mRunLock = new Object();

工作线程中的方法 setRunning class:

public void setRunning(boolean b) {
    synchronized (mRunLock) {
        mRun = b;
    }
}

和方法run相同class:

public void run() {
    while (mRun) {
        Canvas c = null;
        try {
            c = mSurfaceHolder.lockCanvas(null);
            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) updatePhysics();
                synchronized (mRunLock) {
                    if (mRun) doDraw(c);
                }
            }
        } finally {
            if (c != null) {
                mSurfaceHolder.unlockCanvasAndPost(c);
            }
        }
    }
}

while语句中不同步mRun是否正确?我认为 setRunning 可能会在 mRun 被检查为 true 时被调用。

您需要保留 'synchronized' 语句。如果你不这样做(尽管注意 android,它不是真正的 java,可能不遵循与实际 java 相同的内存模型),那么任何线程都可以自由地为它想要的任何实例的任何字段创建一个临时克隆,并在某个未定义的稍后时间点将对克隆的任何写入与任何其他线程的克隆同步。

为了避免这些问题 'clones'*,您需要建立 CBCA 关系 ("comes before/comes after") - 如果线程模型确保线程 A 中的行 X 肯定在 运行 之后线程 B 中的第 Y 行,那么第 Y 行完成的任何字段写入都将保证在第 X 行中可见运行。

换句话说,对于同步语句,如果您的 运行() 方法中的 mRunLock 锁必须 'wait' 才能使 setRunning 方法完成 运行ning,您只需在两者之间建立了 CBCA 关系,这很重要,因为这意味着 setRunning 完成的 mRun 写入现在可见。如果你没有,它可能可见,也可能不可见,这取决于你phone中的芯片和月相。

请注意,布尔值写入在其他方面是原子的。因此,如果您在写入字段时阅读会发生任何问题(如果字段的类型被规定为原子类型,这本身就不是问题,除了 double 和 long 之外的所有基元都是原子的),它是确保任何更改的可见性。

简而言之 java 你可能会为此使用 AtomicBoolean 并避免使用任何同步的东西。另请注意,在不同的锁上嵌套 synchronized()(先锁定 mSurfaceHolder,然后锁定 mRunLock)可能会导致死锁,如果有任何代码这样做的话 'in reverse'(首先锁定 mRunLock,然后锁定 mSurfaceHolder)。

您是否运行遇到此代码的任何问题,或者只是想知道'is it correct'?如果是后者:是的,是正确的。

*) 虽然这个克隆听起来乏味且容易出错,但唯一的选择是任何线程写入的任何字段都立即被任何其他线程可见。那会让一切都慢下来; VM 不知道哪些写入有可能很快被另一个线程读取,如果您对现代 CPU 架构有所了解,每个内核都有自己的高速缓存,数量级(100 到 1000 倍!)比系统内存快。 'all writes must always be visible everywhere' 的这种替代方案几乎意味着字段永远不会在任何缓存中。这对性能来说是灾难性的。因此,这种内存模型基本上是一个必要之恶。有些语言没有它;它们往往比 java.

慢几个数量级

我认为代码不正确。

你可能应该这样做:

while (true) {
  synchronized (mRunLock) {
    if (mRun) break;
  }

  // ...
}

如果没有这个,您无法保证写入 mRun 发生在条件读取之前。

没有它它会有点工作,因为您正在循环内的同步块内读取 mRun;如果执行了读取,则该值将被更新。但是您在下一次迭代中在循环表达式中读取的值可能与在 synchronized (mRunLock) { if (mRun) doDraw(c); }.

中的上一次迭代中读取的值相同

重要的是,不能保证在初始迭代时读取到最新值。如果缓存为 false,则循环不会执行。

不过,使 mRun 可变比使用同步更容易。