假设我有一个包含字段data
的共享对象。多个线程将共享对此对象的引用以访问该字段。但是,线程永远不会同时访问对象。我是否需要将data
声明为volatile?
这种情况如下:
Counter
定义了唯一字段value
和一个方法increment
。 鉴于程序的逻辑,没有对计数器的并发访问。然而,计数器在多个线程中共享。柜台必须是不稳定的吗?
另一种情况是多线程操作一个纯数据的对象X,但是通过另一个依赖并发控制的对象Y来交替它们的临时执行(以便永远不会同时访问X)(wait
,notify
,synchronize
)。对象X的字段是否应该是易变的?
答案 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,则可能存在间接同步对X的访问的对象Y,所以没关系。我看到的唯一不安全的情况是线程是否按时传递(例如使用Thread.sleep或通过循环直到一段时间已经完成)来保证相互排斥:在这种情况下,没有发生在之前建立的关系。