我不确定我在这里正确使用@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
还是以其他方式更改?
答案 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.1,JLS §8.3.1.4)
@volatile
标记一个可以在程序控制之外改变其值的字段;这相当于Java中的volatile
修饰符。
和
对
volatile
字段(第8.3.1.4节)的写入发生在每次后续读取该字段之前。
这里的问题是要么你不需要所有这些,因为对象实际上是不可变的(并且你最好使用一个正确不可变的对象),或者你想看到buf
和size
中的协调变化关于收件人大小的@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()
,因此警告。