0

假设我有一个共享对象 field data。多个线程将共享对该对象的引用以访问该字段。但是,线程永远不会同时访问对象。我需要声明data为 volatile 吗?

这种情况如下:

  • 一个类Counter定义了一个唯一的字段value和一个方法increment
  • 一个线程增加计数器,然后产生另一个增加计数器的线程,等等。

鉴于程序的逻辑,没有对计数器的并发访问。然而,计数器是跨多个线程共享的。计数器必须是易失性的吗?

这种情况的另一种变体是当多个线程操作一个纯数据对象 X 时,但通过另一个依赖于并发控制 ( wait, notify, ) 的对象 Y 交替执行它们的时间执行(这样 X 永远不会同时访问synchronize)。对象 X 的字段应该是可变的吗?

4

5 回答 5

3

强烈建议学习 Java 内存模型的整个 JLS 章节——实际上是强制性的——任何使用 Java 进行并发的人。具体来说,您的案例在JLS, 17.4.4 中有介绍:

“启动线程的动作与它启动的线程中的第一个动作同步。”

这意味着对于您的第一个场景,您不需要volatile. 但是,无论如何,让它对未来的代码更改具有鲁棒性是一种很好的做法。您确实应该有充分的理由拥有volatile,这仅适用于读取率极高(至少每秒数百万)的情况。

于 2012-10-11T11:55:30.917 回答
1

关于问题的第二部分:如果您不在变量 X 上使用 volatile,则给定线程可能始终使用变量值的本地缓存版本。您将变量 Y 用作锁将非常有效,可以确保两个线程不会同时写入 X,但不能保证其中一个线程不会查看过时的数据。

来自 JLS:“对 volatile 变量 v 的写入与任何线程对 v 的所有后续读取同步”。我读这篇文章的方式是,规范不保证读取除 v 之外的其他变量。

于 2012-10-11T19:08:47.027 回答
1

Java 内存模型和字节码重新排序并不能保证后续线程将看到计数器的递增值。因此,如果您使用单线程 - 您不需要对 volatile 执行任何操作,但如果多个线程可能从变量中读取某些内容 - 您需要确保使用 volatile 或使用同步/锁对另一个线程的更改可见性。

Thread.start 方法施加了障碍,因此可以确保可见性 - 并且您可能不需要那些易变的东西。但无论如何我都会添加它。

于 2012-10-11T11:47:40.213 回答
0

你只讲述了柜台故事的一部分。计数器的递增部分似乎很好——正如 Marko 指出的那样,在 Thread.start 处有一个 HB 边缘。但是谁在看这个计数器?如果它是这些衍生线程之外的任何人,并且您完全关心查看最新值,那么该字段需要是可变的。如果计数器是long(或double),即使您不关心过时的值,您也需要它是 volatile 的,否则您可能会导致单词撕裂。

于 2012-10-11T12:47:33.210 回答
0

只有当线程之间的发生之前的关系建立时,才能保证来自线程的突变对其他线程可见。建立关系后,所有先前的突变都变得可见。

如果另一个对象正确同步了对它的访问,则在隔离时未正确同步的对象可以安全使用(请参阅Java Concurrency in Practice 中的捎带)

在问题中描述的两种情况下,我认为不需要同步:

  • Thread.start建立之前发生的关系,因此来自先前线程的所有突变都是可见的
  • 对对象 X 的访问由对象 Y 同步,这将建立先发生关系并使对 X 的更改可见(我在博客文章中进行了更多扩展)。

如果您知道对象 X 永远不会同时访问,那么很可能有一个对象 Y 间接同步了对 X 的访问,所以没关系。我看到的唯一不安全的情况是线程本身是否按时间中继(例如,使用 Thread.sleep 或通过循环直到经过一段时间)以保证互斥:在这种情况下,没有建立之前发生的关系。

于 2013-10-14T06:51:21.003 回答