@volatile usage unclear - 将带有`var`的对象发送到另一个线程

时间:2016-05-18 16:34:55

标签: scala concurrency

我不确定我在这里正确使用@volatile。我有一个缓冲区,如下:

final class BufD(val buf: Array[Double], @volatile var size: Int)

在进程之间发送,它可能跨越线程边界。发件人可以在发送之前更新size字段。因此,我想确保接收器在任何情况下都不能在此处看到陈旧的size值。 第一个问题: @volatile是确保这个还是多余?

现在我介绍一个特性:

trait BufLike {
  @volatile var size: Int
}

final class BufD(val buf: Array[Double], @volatile var size: Int) extends BufLike

这给了我一个编译器警告:

  

警告:(6,4)没有针对方法大小的注释的有效目标 - 它被丢弃未使用。您可以使用元注释指定目标,例如@(volatile @getter)

 @volatile var size: Int
  ^

第二个问题:我应该删除此处的@volatile还是以其他方式更改?

2 个答案:

答案 0 :(得分:6)

我假设线程A创建,更新,然后将对象-X传递给线程-B。如果对象-X及其直接或可传递的任何内容(字段)不再由线程A更新,则volatile是多余的。 JVM保证接收线程的object-X状态的一致性。

换句话说,如果object-X的逻辑所有权从线程A传递到线程B,那么volatile没有意义。相反,在现代多核系统中,volatile的性能影响可能大于不可变案例类留下的线程局部垃圾。

如果要将object-X共享用于写入,则创建字段volatile将有助于共享其值,但是您将面临另一个问题:对象-X上的非原子更新,如果是字段'价值取决于彼此。

正如@alf指出的那样,为了从发生 - 保证之前受益,必须安全地传递对象!这可以使用java.util.concurrent.**类来实现。像Akka这样的高级结构定义了他们自己“安全地”传递对象的机制。

参考文献:

https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html

答案 1 :(得分:3)

正如@tair指出的那样,问题的真正解决方案是使用不可变的案例类:

  

发件人可以在发送之前更新尺寸字段。

接收器似乎更新大小;如果已经发送BufD,发件人也不会更新大小。因此,出于所有实际原因,收件人收到不可变对象要好得多。

对于@volatile,它确保可见性 - 写入确实命中主内存而不是缓存在线程本地缓存中,并且读取包括内存屏障以确保读取的值不是陈旧的。

没有@volatile,收件人线程可以自由缓存该值(它不是易失性的,因此不应该从其他线程更改,因此可以安全地缓存)并重新使用它而不是引用主要记忆。 (SLS 11.2.1JLS §8.3.1.4

  

@volatile标记一个可以在程序控制之外改变其值的字段;这相当于Java中的volatile修饰符。

  

volatile字段(第8.3.1.4节)的写入发生在每次后续读取该字段之前。

这里的问题是要么你不需要所有这些,因为对象实际上是不可变的(并且你最好使用一个正确不可变的对象),或者你想看到bufsize中的协调变化关于收件人大小的@volatile。在后一种情况下,buf可能有用(虽然很脆弱),如果writer将(不覆盖!)附加到size,然后更新buf。在这种情况下,请写入size happens-before写入size,然后发生 - 之前读者可以从volatile读取更新后的值(因此,如果读者检查并重新检查尺寸,并且作者只是追加,那么你可能没什么问题。话虽如此,我不会使用这种设计。

至于警告,它全部编译为Java,即JVM,字节码,var x: T字段的JVM标志。特征不能定义字段 - 它们只定义方法,而是由扩展类决定它是否是适当的变量或(一对)方法(SLS 4.2)。

  

变量声明x等同于 getter函数x_= setter函数def x: T def x_= (y: T): Unit 的声明:

@volatile

函数不能是stopwatchreportInstitution()/startwatchreportInstitution(),因此警告。