【问题标题】:memory visibility on lock acquirement锁定获取的内存可见性
【发布时间】:2013-06-22 22:38:16
【问题描述】:

内存可见性是否取决于所使用的监视器?锁B是在锁A释放后获取的,内存可见性够吗?

例如以下代码:

int state; // shared


// thread A
synchronized (A) {
 state += 1;
}
Thread.sleep(10000000);

// thread B
Thread.sleep(1000);
synchronized(B) {
 state += 1;
}

线程同时启动,线程B的休眠时间可以任意高,只是为了保证在线程A使用state变量之后执行。线程A 睡眠时间用于确保线程在B 使用state 共享变量之前不会完成。

更新

来自http://www.ibm.com/developerworks/library/j-jtp03304/

When a thread exits a synchronized block as part of releasing the associated monitor, the JMM requires that the local processor cache be flushed to main memory.

Similarly, as part of acquiring the monitor when entering a synchronized block, local caches are invalidated so that subsequent reads will go directly to main memory and not the local cache.

如果这是真的,那么我认为state 变量对线程B 不可见

此外,他们说监视器应该是相同的,但上述陈述并未暗示。

This process guarantees that when a variable is written by one thread during a synchronized block protected by a given monitor and read by another thread during a synchronized block protected by the same monitor, the write to the variable will be visible by the reading thread. 

似乎本地内存刷新的过程并不像第一个语句中描述的那么简单,并且可能不会在每个锁释放时发生?

【问题讨论】:

  • happens-before 关系是二元的,即仅在两个事件之间。虽然它是可传递的。如果 A 发生在 B 之前,您只能从 B 获得 A 的内存可见性。
  • @selig,但Thread.sleep 是否添加了happens before 关系?
  • 否 - 如 JLS 中的详细 here 所述,SleepYield 都没有同步语义,即参与发生前的关系。

标签: java concurrency


【解决方案1】:

是的,这取决于。你可以阅读这个doc 关于这个。相关章节为“17.4.4.同步顺序”:

监视器 m 上的解锁操作与 m 上的所有后续锁定操作同步(其中“后续”根据同步顺序定义)。

你看,在那里指定了一个具体的监视器对象m。如果监视器不同,那么您不会获得 synchronizes-with 关系,因此,您不会获得happens-before 关系(从 17.4.5 开始):

如果动作 x 与后续动作 y 同步,那么我们也有 hb(x, y)。

因此,您的更新将无序执行,可能会丢失更新。

【讨论】:

  • 是的,我读过那个文档。但在我看来,由于缺乏互斥,没有hb 关系。但是,在我的示例中,等待可以保证互斥。如果文档也描述了我的情况,那么如果在锁定释放时将本地内存刷新到全局内存,state 对线程B 不可见怎么可能发生?
  • 可能有看似正确的行为,但我认为这取决于实施。据我了解,JVM 规范并没有具体说明刷新的执行方式和时间。所以你不能依赖这种行为。
【解决方案2】:

内存可见性是否取决于所使用的监视器? 是的。

锁A释放后获取锁B,内存可见性够吗? 没有。

两个线程必须在同一个监视器上同步才能看到彼此的写入。在您的示例中,两个线程都可以看到 state 的值为 1。无论您插入什么睡眠间隔。这当然取决于您使用的 JVM 的实现,不同的 JVM 可能会产生不同的结果。基本上,您对字段的访问是非同步的,应该始终避免这种情况(因为 state 的值不确定)。

阅读 Java 规范中关于 Memory Model 的精彩章节了解更多信息。

【讨论】:

  • 这有点奇怪。据我了解,有两种内存类型,线程本地(寄存器、处理器缓存等)和全局内存。在获得锁时,本地内存被清除并从全局内存中读取值,在锁释放时,本地内存被刷新到全局内存。你能解释一下为什么状态如果被刷新到全局内存(Arelease)然后被强制读取(B获取)可能不可见
  • 或者本地内存在锁释放时没有刷新到全局内存并且处于锁本地内存中(线程释放锁和将获取锁的线程可见)?
  • 这基本上是因为两个线程没有建立happens-before关系。因此 JVM 不会将更改“刷新”到主内存并可以缓存本地版本。为什么不应该缓存它?加速可能是巨大的,程序员并没有表示他希望两个线程进行写通信(即因为缺少同步)。
  • From ibm.com/developerworks/library/j-jtp03304 When a thread exits a synchronized block as part of releasing the associated monitor, the JMM requires that the local processor cache be flushed to main memory. 看来锁释放应该总是刷新本地内存。如果是这样,那么我不明白为什么 state 可能对线程 B 不可见
  • 根据 Java 语言规范,仅当另一个线程在同一监视器上获得锁时(如线程 #1)。我已阅读您链接到的 IBM 参考资料,它与规范一致。我只能建议你喝杯好咖啡并阅读规范中的第 17 章。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-29
  • 2012-09-08
  • 1970-01-01
相关资源
最近更新 更多