空的 synced(this){} 对线程之间的内存可见性有什么意义吗?

我在StackOverflow上的一篇投票评论中读到了这一点:

但是如果你想安全,你可以在末尾添加简单的synced(this){}@PostConstruct[方法]

[请注意,变量不是易失性的]

我在想,只有当写入和读取都在块中执行或至少读取是易失性的时,才会强制发生之前。synchronized

引用的句子是否正确?空块是否会将当前方法中更改的所有变量刷新为“一般可见”内存?synchronized(this) {}

请考虑一些风景

  • 如果第二个线程从不调用锁定怎么办?(假设第二个线程在其他方法中读取)。请记住,这个问题是关于:刷新对其他线程的更改,而不是为其他线程提供一种(同步)来轮询原始线程所做的更改的方法。在其他方法中,其他方法中的无同步也很可能在Spring上下文中 - 正如原始评论所说。this@PostConstruct

  • 更改的内存可见性是否仅在另一个线程的第二次和后续调用中强制执行?(请记住,此同步块是我们方法中的最后一次调用) - 这会将这种同步方式标记为非常糟糕的做法(第一次调用中的过时值)


答案 1

可悲的是,SO上关于这个问题的大部分内容,包括这个帖子中的许多答案/评论,都是错误的。

此处适用的 Java 内存模型中的关键规则是:在给定监视器上执行解锁操作之前,在同一监视器上执行后续锁定操作之前。如果只有一个线程获取锁,则没有任何意义。如果 VM 可以证明锁定对象是线程受限的,则可以消除它可能发出的任何栅栏。

您突出显示的报价假设释放锁充当完整的围栏。有时这可能是真的,但你不能指望它。所以你的怀疑问题是有根据的。

有关 Java 内存模型的更多信息,请参阅 Java 并发实践,第 16 章。


答案 2

在 之前发生的所有写入对 a 之后的所有线程都可见。monitor exitmonitor enter

A可以变成字节码,如synchronized(this){}

monitorenter
monitorexit

因此,如果您在 之前有一堆写入,它们将发生在 .synchronized(this){}monitorexit

这就引出了我第一句话的下一点。

对之后的所有线程可见monitor enter

所以现在,为了使线程确保写入操作,它必须执行相同的同步,即。这将至少发出a,并在订购前确定您的发生。synchornized(this){}monitorenter


所以回答你的问题

空的 synced(this) {} 块是否会将当前方法中更改的所有变量刷新为“常规可见”内存?

是的,只要您在要读取这些非易失性变量时保持相同的同步。

解决您的其他问题

如果第二个线程从不对此调用锁定怎么办?(假设第二个线程在其他方法中读取)。请记住,这个问题是关于:刷新对其他线程的更改,而不是为其他线程提供一种(同步)来轮询原始线程所做的更改的方法。在其他方法中,无同步也很可能在Spring @PostConstruct上下文中

好吧,在这种情况下,在没有任何其他上下文的情况下使用是相对无用的。没有发生之前的关系,从理论上讲,它与不包括它一样有用。synchronized(this)

更改的内存可见性是否仅在另一个线程的第二次和后续调用中强制执行?(请记住,此同步块是我们方法中的最后一次调用) - 这会将这种同步方式标记为非常糟糕的做法(第一次调用中的过时值)

内存可见性是由第一个线程调用强制的,因为它将直接写入内存。现在,这并不一定意味着每个线程都需要直接从内存中读取。他们仍然可以从自己的处理器缓存中读取。具有线程调用可确保它从内存中提取字段的值并检索最新的值。synchronized(this)synchronized(this)


推荐