Reputation: 67300
I am not sure I use @volatile
correctly here. I have a buffer, like this:
final class BufD(val buf: Array[Double], @volatile var size: Int)
Which is sent between processes, whereby it might cross thread boundaries. The sender may update the size
field just before sending it out. Therefore I want to make sure that the receiver under no circumstances can see a stale size
value here. First question: Does @volatile
ensure this or is it redundant?
Now I am introducing a trait:
trait BufLike {
@volatile var size: Int
}
final class BufD(val buf: Array[Double], @volatile var size: Int) extends BufLike
This gives me a compiler warning:
Warning:(6, 4) no valid targets for annotation on method size - it is discarded unused. You may specify targets with meta-annotations, e.g. @(volatile @getter)
@volatile var size: Int ^
Second question: Should I remove the @volatile
here or change it in a different way?
Upvotes: 7
Views: 9809
Reputation: 3819
I assume thread-A creates, updates, then passes the object-X to thread-B. If object-X and whatever it refers to directly or transitively (fields) are no further updated by thread-A, then volatile
is redundant. The consistency of the object-X state at the receiving thread is guaranteed by JVM.
In other words, if logical ownership for object-X is passed from thread-A to thread-B, then volatile
doesn't make sense. Conversely, on modern multicore systems, the performance implications of volatile
can be more than that of thread-local garbage left by immutable case classes.
If object-X is supposed to be shared for writing, making a field volatile
will help to share its value, but you will face another problem: non-atomic updates on the object-X, if fields' values depend on each other.
As @alf pointed out, to benefit from happens-before guarantees, the objects must be passed safely! This can be achieved using java.util.concurrent.**
classes. High level constructs like Akka define their own mechanisms of "passing" objects safely.
References:
https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html
Upvotes: 7
Reputation: 8513
As @tair points out, the real solution to your problem is to use an immutable case class:
The sender may update the size field just before sending it out.
It seems that receiver does not update the size; neither does sender update the size after if has already sent the BufD
out. So for all practical reasons, recipient is much better off receiving an immutable object.
As for @volatile
, it ensures visibility—the writes are indeed hitting the main memory instead of being cached in the thread local cache, and the reads include a memory barrier to ensure that the value read is not stale.
Without @volatile
, the recipient thread is free to cache the value (it's not volatile, hence it should not be changed from the other thread, hence it's safe to cache) and re-use it instead of referring to the main memory. (SLS 11.2.1, JLS §8.3.1.4)
@volatile
Marks a field which can change its value outside the control of the program; this is equivalent to thevolatile
modifier in Java.
and
A write to a
volatile
field (§8.3.1.4) happens-before every subsequent read of that field.
The problem here is that either you don't need all that as the object is effectively immutable (and you're better off with a properly immutable one), or you want to see coordinated changes in buf
and size
on the recipient size. In the latter case, @volatile
may be useful (while fragile), if writer appends (not overwrites!) to buf
, and then updates the size
. In this case, write to buf
happens-before write to size
, which in turn happens-before reader can read the updated value from size
(by volatility), therefore if reader checks and re-checks the size, and writer only appends, you're probably fine. Having said that, I would not use this design.
As for the warning, it all compiles to Java, i.e. JVM, bytecode, and volatile
is a JVM flag for fields. Traits cannot define a field—they only define methods, and it's up to the extending class to decide whether it'll be a proper variable or (a pair of) methods (SLS 4.2).
A variable declaration
var x: T
is equivalent to the declarations of both a getter functionx
and a setter functionx_=
:def x: T def x_= (y: T): Unit
A function cannot be @volatile
, hence the warning.
Upvotes: 3