另一种情况,基于之前的问题。在我看来,它的结论足够普遍,对广大受众有用。引自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()
完成的所有更改。也就是说,现在一切都很安全。另外,方法调用的逻辑顺序如下:
operationA1()
operationB()
dummyA()
operationA2()
我的结论:由于operationB()
中的同步块, threadB 将看到之前可能已更改的最新数据值(例如{{1} }})。由于operationA1()
中的同步块, threadA 将查看dummyA()
中更改的最新数据副本。这一思路是否有任何错误?
答案 0 :(得分:2)
一般来说,你对问题2的直觉是正确的。在操作A2开始时使用synchronized(mLock)将发出一个内存屏障,这将确保操作A2的进一步读取将看到操作B执行的写入,这些操作由于使用synchronized隐含的内存屏障而已发布( mLock)在操作B。
但是,要回答问题1,请注意,除非在operationA1结束时插入完整的内存屏障,否则operationB可能看不到operationA1执行的任何写操作(即,没有任何信息告诉系统从operationA1线程的缓存中刷新值) 。因此,您可能希望在operationA1结束时调用dummyA。
为了完全安全且更易于维护,并且由于您声明这些方法的执行不会相互重叠,因此您应该在同步(mLock)块中包含对共享状态的所有操作,而不会降低性能。