根据先前的问题,还有另一种情况。我认为,其结论将具有普遍意义,对广大受众有用。从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)块中,而不会降低性能。