另一种情况,基于之前的问题。我认为,其结论足够普遍以对广大受众有用。引用 Peter Lawrey 在此处的话:
同步使用内存屏障,确保该线程内所有内存处于一致状态,无论是在块内还是不在块内引用。
首先,我的问题仅涉及数据可见性。也就是说,原子性(“操作同步”)已在我的软件中得到了保证,因此在相同值上进行任何写操作之前,每个读操作都已完成,反之亦然,等等。因此,问题只涉及线程可能缓存的值。
考虑 2 个线程 threadA 和 threadB,以及以下类:
已知事实(它们来自于我的软件架构):
-
根据Peter Lawrey的引用,
问题#2:当
同步使用内存屏障,确保该线程内所有内存处于一致状态,无论是在块内还是不在块内引用。
首先,我的问题仅涉及数据可见性。也就是说,原子性(“操作同步”)已在我的软件中得到了保证,因此在相同值上进行任何写操作之前,每个读操作都已完成,反之亦然,等等。因此,问题只涉及线程可能缓存的值。
考虑 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()
被线程A调用,operationB()
被线程B调用。
- 在这个类中,operationB()
是线程B唯一调用的方法。请注意,operationB()
在同步块中。
- 非常重要:保证以以下逻辑顺序调用这些操作:operationA1()
、operationB()
、operationA2()
。保证每个操作在前一个操作被调用之前完成。这是由于更高级别的架构同步(消息队列,但现在不相关)。正如我所说,我的问题纯粹与数据可见性有关(即数据副本是否最新或过时,例如由于线程自己的缓存)。根据Peter Lawrey的引用,
operationB()
中的内存屏障确保在operationB()
期间,所有内存都处于一致状态,对于线程B。因此,例如,如果线程A在operationA1()
中更改了某些值,则这些值将从线程A的缓存中写入主内存,到operationB()
开始时。问题#1:这正确吗?问题#2:当
operationB()
离开内存屏障时,由operationB()
更改的值(可能由线程B缓存)将被写回主内存。但是,operationA2()不安全,因为没有人要求线程A与主内存同步,对吧?因此,即使operationB()
的更改现在在主内存中,也无关紧要,因为线程A可能仍然具有其在调用operationB()
之前的缓存副本。
问题 #3: 如果我的 Q.#2 中的怀疑是正确的,则请再次检查我的源代码并取消注释 dummyA()
方法,并取消注释 operationA2()
中对 dummyA()
的调用。我知道这可能在其他方面是不好的做法,但这有什么区别吗?我的(可能错误的)假设如下:dummyA()
将导致 threadA 更新其缓存数据来自主内存(由于 mLock
同步块),因此它将看到由 operationB()
进行的所有更改,即现在一切都是安全的。顺便说一下,方法调用的逻辑顺序如下:
operationA1()
operationB()
dummyA()
operationA2()
我的结论:由于 operationB()
中的同步块,threadB 将看到可能在之前被更改的数据的最新值(例如,在 operationA1()
中)。由于 dummyA()
中的同步块,threadA 将看到在 operationB()
中更改的数据的最新副本。这个思路有什么错误吗?