多线程场景中的数据可见性

时间:2012-07-03 01:23:02

标签: java android multithreading concurrency synchronized

另一种情况,基于之前的问题。在我看来,它的结论足够普遍,对广大受众有用。引自here的Peter Lawrey:

  

synchronized使用内存屏障确保所有内存都在   该线程的一致状态,是否在其中引用   阻止与否。

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

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

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

基于Peter Lawrey的引用,operationB()中的内存障碍确保threadB期间operationB()的所有内存都处于一致状态。因此,例如如果 threadA 更改了operationA1()中的某些值,则这些值将在 threadA 的缓存中被operationB()时写入主内存。开始。 问题#1 :这是正确的吗?

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

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

  1. operationA1()
  2. operationB()
  3. dummyA()
  4. operationA2()
  5. 我的结论:由于operationB()中的同步块, threadB 将看到之前可能已更改的最新数据值(例如{{1} }})。由于operationA1()中的同步块, threadA 将查看dummyA()中更改的最新数据副本。这一思路是否有任何错误?

1 个答案:

答案 0 :(得分:2)

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

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

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