0__
0__

Reputation: 67300

@volatile usage unclear - sending an object with a `var` to another thread

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

Answers (2)

Tair
Tair

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

alf
alf

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 the volatile 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 function x and a setter function x_=:

def x: T
def x_= (y: T): Unit

A function cannot be @volatile, hence the warning.

Upvotes: 3

Related Questions