另一个场景,基于前面的问题。在我看来,它的结论将足够笼统,对广大受众有用。从这里引用彼得劳里:
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()
并由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()
. 也就是说,现在一切都安全了。另外,方法调用的逻辑顺序如下:
operationA1()
operationB()
dummyA()
operationA2()
我的结论:由于 中的同步块operationB()
,threadB将看到之前可能已更改的数据的最新值(例如在 中operationA1()
)。由于 中的同步块dummyA()
,threadA将看到在 中更改的数据的最新副本operationB()
。这个思路有什么错误吗?