3

另一个场景,基于前面的问题。在我看来,它的结论将足够笼统,对广大受众有用。从这里引用彼得劳里:

synchronized 使用内存屏障,确保所有内存都处于该线程的一致状态,无论它是否在块内引用。

首先,我的问题只涉及数据可见性。也就是说,我的软件已经保证了原子性(“操作同步”),因此每个写操作都在对同一值的任何读操作之前完成,反之亦然,依此类推。所以问题只是关于线程可能缓存的值。

考虑 2 个线程,threadAthreadB以及以下类:

public class SomeClass {

private final Object mLock = new Object();    
// Note: none of the member variables are volatile.

public void operationA1() {
   ... // do "ordinary" stuff with the data and methods of SomeClass

     /* "ordinary" stuff means we don't create new Threads,
         we don't perform synchronizations, create semaphores etc.
     */
}

public void operationB() {
  synchronized(mLock) {
     ...
     // do "ordinary" stuff with the data and methods of SomeClass
  }
}

// public void dummyA() {
// synchronized(mLock) {
//    dummyOperation();
//  }
// }

public void operationA2() {
   // dummyA();  // this call is commented out

   ... // do "ordinary" stuff with the data and methods of SomeClass
}
}

已知事实(它们来自我的软件架构):

  • operationA1()并由threadAoperationA2()调用,由threadB调用operationB()
  • operationB()是这个类中threadB调用的唯一方法。请注意,它位于同步块中。operationB()
  • 非常重要:保证这些操作按以下逻辑顺序调用:operationA1(), operationB(), operationA2(). 保证在调用前一个操作之前完成每个操作。这是由于更高级别的架构同步(消息队列,但现在无关紧要)。正如我所说,我的问题纯粹与数据可见性有关(即数据副本是最新的还是过时的,例如由于线程自己的缓存)。

根据 Peter Lawrey 的名言,内存屏障 inoperationB()确保所有内存在threadB期间保持一致状态operationB()。因此,例如如果threadA改变了一些值operationA1(),这些值将在threadA启动时从cache中写入主存operationB()问题#1:这是正确的吗?

问题 #2:当operationB()离开内存屏障时,由 threadB 更改的值operationB()(并且可能由threadB缓存)将被写回主内存。但是 operationA2() 不会是安全的,因为没有人要求threadA与主内存同步,对吧?operationB()因此,现在在主内存中的更改并不重要,因为threadA可能仍然具有operationB()调用前一次的缓存副本。

问题 #3:如果我在 Q.#2 中的怀疑是真的,那么再次检查我的源代码并取消注释方法dummyA(),并取消注释中的dummyA()调用operationA2()。我知道这在其他方面可能是不好的做法,但这有什么不同吗?我的(可能是错误的)假设如下:dummyA()将导致threadA从主内存更新其缓存数据(由于mLock同步块),因此它将看到由operationB(). 也就是说,现在一切都安全了。另外,方法调用的逻辑顺序如下:

  1. operationA1()
  2. operationB()
  3. dummyA()
  4. operationA2()

我的结论:由于 中的同步块operationB()threadB将看到之前可能已更改的数据的最新值(例如在 中operationA1())。由于 中的同步块dummyA()threadA将看到在 中更改的数据的最新副本operationB()。这个思路有什么错误吗?

4

1 回答 1

2

一般来说,您对问题 2 的直觉是正确的。在操作 A2 开始时使用 synchronized(mLock) 将发出一个内存屏障,这将确保操作 A2 的进一步读取将看到操作 B 执行的写入,由于使用 synchronized( mLock) 运行中 B.

但是,要回答问题 1,请注意 operationB 可能看不到 operationA1 执行的任何写入,除非您在 operationA1 的末尾插入完整的内存屏障(即,没有任何东西告诉系统从 operationA1 线程的缓存中刷新值)。因此,您可能希望在 operationA1 结束时调用 dummyA。

为了完全安全和更易于维护,并且由于您声明这些方法的执行不相互重叠,您应该将所有共享状态的操作包含在 synchronized(mLock) 块中,而不会损失性能。

于 2012-07-03T02:14:54.970 回答