同步跨线程共享但不同时访问的对象

时间:2012-10-11 11:45:04

标签: java concurrency volatile

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

这种情况如下:

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

鉴于程序的逻辑,没有对计数器的并发访问。然而,计数器在多个线程中共享。柜台必须是不稳定的吗?

另一种情况是多线程操作一个纯数据的对象X,但是通过另一个依赖并发控制的对象Y来交替它们的临时执行(以便永远不会同时访问X)(waitnotifysynchronize)。对象X的字段是否应该是易变的?

5 个答案:

答案 0 :(得分:3)

强烈建议研究Java内存模型的整个JLS章节 - 强制,实际上 - 对于在Java中进行并发的人来说。具体而言,您的案例包含在JLS, 17.4.4:

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

这意味着对于您的第一个场景,您不需要volatile。但是,无论如何都要保持对代码的未来更改是健全的。你应该有一个很好的理由来拥有volatile,这只有在读取率非常高的情况下(至少每秒数百万)。

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

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

来自JLS:“写入易失性变量v与任何线程的所有后续v读取同步”。我读这个的方式是规范不保证除了v。

之外对其他变量的读取

答案 3 :(得分:0)

你只是通过柜台告诉了部分故事。计数器的递增部分似乎很好 - 正如Marko指出的那样,Thread.start上有一个HB边缘。但谁在阅读这个柜台呢?如果它是除了这些衍生线程之外的任何人,并且你完全关心看到最新的值,那么该字段需要是不稳定的。如果计数器是long(或double),即使您不关心陈旧值,也需要它是易变的,否则您可能会撕裂。

答案 4 :(得分:0)

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

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

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

  • Thread.start建立了一个先发生过的关系,所以以前线程的所有突变都是可见的
  • 对象X的访问由对象Y同步,它将建立先发生关系并使对X的更改可见(我在a blog post中扩展了一点)。

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