Java中synchronized关键字的记忆效应
这可能之前已经得到解答,但由于问题的复杂性,我需要确认。 所以我重新解释这个问题
问题1 :当线程进入同步块时,内存屏障将包括触摸的任何字段,而不仅仅是我同步的对象的字段? 因此,如果在同步块内修改了许多对象,那么在线程内存缓存之间会有大量内存移动。
Thread 1 object.field1 = ""; synchronized (lock) { farAwayObject.field1 = ""; farAwayObject.evenFarther.field2 = ""; } Thread 2. assuming thread ordering is correct synchronized (lock) { //thread 2 guaranteed to see all fields above as "" //even object.field1 ? }
问题2 :是object.field1 = "";
在线程1中隐含地部分发生在之前的关系?
我希望它是,但它可能不会。 如果没有,是否有一个技巧,使它没有把它放入同步块? 否则很难对该程序进行推理,将所有内容置于synchronized {}之下是不切实际的。
编辑:澄清:object.field1不是易失性的,问题是“将线程2保证看到线程1的写入,至少”。 我的问题是内存可见性。 为了论证,假设只有线程1写入非易失性object.field1。
问题2可以改为
“锁定推送上的同步块是否会在同一个锁上同步的其他线程看到之前发生变化?”
当线程进入同步块时,内存屏障将包括所触摸的任何字段,而不仅仅是我同步的对象的字段
假设farAwayObject
和evenFarther
的字段总是通过在应用程序周围获取对同一对象的lock
来修改和接受,所有线程将始终看到对farAwayObject
和evenFarther
的更新,因为synchronized
强制执行before-before条件。
//thread 2 guaranteed to see all fields above as ""
对于object.field1
不知道如何声明它是不一样的。 假设field1
是一个未标记为volatile
的引用,它将不会成为关系之前发生的一部分,并且线程可能会看到它的陈旧值。
我希望它是,但它可能不会。 如果没有,是否有一个技巧,使它没有把它放入同步块?
是。 将object.field1
标记为volatile
。
添加您的编辑:
问题2可以改为
“锁定推送上的同步块是否会在同一个锁上同步的其他线程看到之前发生变化?”
AFAIK的答案是肯定的,前提是写线程在读取线程之前获取锁定。 不幸的是,这是你通常无法保证的,这就是为什么object.field1
需要被标记为volatile
,语句需要在synchronized
块内移动。
看看JSR 133 ,它讲述了当线程退出 syncronized
块时缓存被刷新到主内存。 这应该进一步澄清事情。
1)当线程进入同步块时,内存屏障将包括触摸的任何字段,而不仅仅是我同步的对象的字段?
正确。 (假设线程1和线程2在同一个锁上同步。)
因此,如果在同步块内修改了许多对象,那么在线程内存缓存之间会有大量内存移动。
可能,是的。 但是,它(可能)不是缓存之间的移动。 更有可能的是,它是从一个处理器的缓存到内存,从内存到第二个处理器缓存的移动。 当然,这取决于硬件如何实现内存层次结构。
2)是object.field1 =“”; 在线程1中隐含地部分发生在之前的关系?
有一系列发生在前的关系
- 对
object.field1
的写入发生在获取锁之前。 - 这发生在写入
farAwayObject
之前,等等。 - 这发生在线程1释放锁之前
- 这发生在线程2获取锁之前
- 这发生在线程2读取
object.field1
之前。
问题是如果在由线程1获取lock
之前或者通过某个其他线程对object.field1
进行插入写入会发生什么。 在任何一种情况下,before-before链都不足以确保线程2看到线程1写入的值。
内存屏障将包括触摸的任何字段,而不仅仅是我同步的对象的字段?
是。
是object.field1 =“”; 在线程1中隐含地部分发生在之前的关系?
是的,即使它不是不稳定的。
发生之前的订单是部分订单。
发生之前的顺序由同步的传递闭包 – 边缘和程序顺序给出。 它必须是有效的偏序:反身, 传递和反对称。
( JLS 17.4.7 )
同步边缘之前的操作(即同步锁定的释放)按程序顺序排序,因此与发布同步。 Transitivity表示通过在另一个线程中获取相同锁定而排序的操作因此具有一个发生前的顺序,同时释放该锁定和释放该锁定之前的操作,无论它是否在synchronized
块。 要记住这一点的重要一点是,对动作(即获取/释放锁)的排序不是一个块,例如synchronized关键字的括号所隐含的。 括号表示获取/释放操作的位置以及一组操作无法交错的位置。
最后,请记住,之前发生的是“部分”订单。 它的意思是:
- 当动作恰好以特定顺序出现(即释放/获取,写入/读取等)时,会强制执行内存一致性
- 之前发生的事情取决于更强的保证,例如程序顺序,以产生正确的function。
- 之前发生的事情并不能防止来自交叉非primefaces动作的错误
- 之前发生的是行动和强烈行动之间的传递关系。 (只要写入到块之前并且读取在块之后,就可以将共享变量的读取放在
synchronized
块之外)。 更强的订购保证也会在订购之前发生,但它们也提供了规范中描述的其他效果。