How does final field works without synchronization

Viewed 39

"Java Memory Model" in "Chapter 2 Concurrency on the JVM and the Java Memory Model" of "Learning Concurrent Programming in Scala" states that

If an object contains only final fields and the reference to the enclosing object does not become visible to another thread before the constructor completes, then the object is considered immutable and can be shared between the threads without any synchronization.

This sounds logical. A final field will not have any mutation and thus there is no data races to worry about that object monitors should be called upon to avoid.

The author then provides an example involving closure

var inc: () => Unit = null 
val t = thread { if (inc != null) inc() } 
private var number = 1 
inc = () => { number += 1 } 

The local number variable is captured by the lambda, so it needs to be lifted. The statement in the last line translates to an anonymous Function0 class instantiation:

number = new IntRef(1) // captured local variables become objects 
inc = new Function0 { 
  val $number = number // recall - vals are final! 
  def apply() = $number.elem += 1 
} 

There is no happens-before relationship between the assignment to inc and the read of inc by the thread t. However, if the t thread sees that inc is not null, invoking inc still works correctly, because the $number field is appropriately initialized since it is stored as a field in the immutable lambda object.

What I am trying to understand:

  1. Does inc != null ensure that all of the variables referenced by Function0 object will be properly initialized? Which JMM property ensures that since t is not in happens-before relationship with number? Or is it more of property of the object which ensure that it is accessible only after it is properly initialized?
  2. What happens if number is set to 2 before t thread evaluates inc? Does t evaluate 3? $number is an object reference and even though it is a val, the value can change even though reference stays the same.
1 Answers

I think that book is wrong. There is no guarantee that a reference assigned to inc is completely initialized.

In fact, I don't see anything preventing a sufficiently smart optimizer from removing that entire snippet and never starting the thread to begin with: it is not guaranteed to ever see inc != null, and as such, could be entirely removed.

#2 Isn't really much more different. IntRef is just pushing the can down the road. Sure, it's final (you are missing a val in your snippet) but it's insides are not. intRef.foo += 1 is the same as just foo += 1 for all practical purposes.

Bottom line is, avoid mutability, especially in scala. If you are making something mutable, you better know exactly why you have to in that specific case. 99% of time you don't really need it. 99.9% really, if you use libraries.

Related