根据先前的问题,还有另一种情况。我认为,其结论将具有普遍意义,对广大受众有用。从here引用Peter Lawrey:

同步使用内存屏障,以确保所有内存都在
该线程的一致状态,无论其是否在
是否阻止。

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

考虑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()中的某些值,则在operationB()启动时,这些值将从threadA的高速缓存写入主内存。 问题#1 :这正确吗?

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

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

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

    最佳答案

    通常,您对问题2的直觉是正确的。在operationA2的开始使用synchronized(mLock)将发出内存屏障,这将确保操作A2进行的进一步读取将看到操作B执行的写操作,这些操作已发布,这是由于使用synced( B)。

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

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

    08-17 02:41