3

From the «Learning concurrent programming in Scala» book:

In current versions of Scala (2.11.1), however, certain collections that are deemed immutable, such as List and Vector, cannot be shared without synchronization. Although their external API does not allow you to modify them, they contain non-final fields.

Could anyone demonstrate this with a small example? And does this still apply to 2.11.7?

qed
  • 22,298
  • 21
  • 125
  • 196
  • Possible duplicate of [Must access to scala.collection.immutable.List and Vector be synchronized?](http://stackoverflow.com/questions/29713665/must-access-to-scala-collection-immutable-list-and-vector-be-synchronized) – Łukasz Jan 16 '16 at 22:11
  • 2
    See https://issues.scala-lang.org/browse/SI-7838 and https://groups.google.com/forum/#!topic/scala-internals/P7WNVkJQdHk – ghik Jan 16 '16 at 22:28

2 Answers2

1

The behavior of changes made in one thread when viewed from another is governed by the Java Memory Model. In particular, these rules are extremely weak when it comes to something like building a collection and then passing the built-and-now-immutable collection to another thread. The JMM does not guarantee that the other thread won't see an earlier view where the collection was not fully built!

Since synchronized blocks enforce an ordering, they can be used to get a consistent view if they're used on every single operation.

In practice, though, this is rarely actually necessary. On the CPU side, there is typically a memory barrier operation that can be used to enforce memory consistency (i.e. if you write the tail of your list and then pass a memory barrier, no other thread can see the tail un-set). And in practice, JVMs usually have to implement synchronized by using memory barriers. So one could hope that you could just pass the created list within a synchronzied block, trusting that a memory barrier would be issued, and everything thereafter would be fine.

Unfortunately, the JMM doesn't require that it be implemented in this way (and you can't assume that the memory-barrier-like behavior of object creation will actually be a full memory barrier that applies to everything in that thread as opposed to simply the final fields of that object), which is both why the recommendation is what it is, and why it's not fixed (yet, anyway) in the library.

For what it's worth, on x86 architectures, I've never observed a problem if you hand off the immutable object within a synchronized block. I have observed problems if you try to do it with CAS (e.g. by using the java.util.concurrent.atomic classes).

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • At least using an `java.util.concurrent.atomic.AtomicReference` should be safe, since the memory effects for accesses and updates of atomics generally follow the rules for volatiles [docs](The memory effects for accesses and updates of atomics generally follow the rules for volatiles), right? – Rüdiger Klaehn Jan 17 '16 at 09:28
  • @RüdigerKlaehn - You can achieve similar effects with memory barriers and compare-and-swap, but AFAIK the atomic classes use CAS wherever possible because they usually perform better. So although I don't have an example of a failure from inside an `AtomicReference`, I am not confident that there could not be one. – Rex Kerr Jan 17 '16 at 20:58
1

As an addition to the excellent answer from Rex Kerr:

it should be noted that most common use cases of immutable collections in a multithreading context are not affected by this problem. The only situation where this might affect you is when you do something that you probably should not do in the first place.

E.g. you have a variable var x: Vector[Int], which you write from one thread A and read from another thread B.

If you mark x with @volatile, there will be no problem, since the volatile write introduces a memory barrier. So you will never be able to observe the Vector in an inconsistent state. The same is true when using a synchronized { } block when writing and reading, or when using java.util.concurrent.atomic.AtomicReference.

If you don't mark x with @volatile, you might observe the vector in an inconsistent state (not just wrong elements, but internally inconsistent!). But in that case your code is arguably broken to begin with. It is completely undefined when you will see the changes from A in B.

You might see them

  • immediately
  • after there is a memory barrier somewhere else in your program
  • not at all

depending on the architecture you`re running on, the phase of the moon, whatever. So as Viktor Klang put it: "Unsafe publication is unsafe..."

Note that if you use a higher level concurrency framework such as akka actors, it is also guaranteed that receivers of messages can not see immutable collections in an inconsistent state.

Community
  • 1
  • 1
Rüdiger Klaehn
  • 12,445
  • 3
  • 41
  • 57
  • Note that the JMM doesn't actually guarantee that your vector is safe if you mark it with @volatile, just that you'll see the outer class correctly (not anything it points to internally). I really wish the JMM were a little stronger so you could count on the memory barrier actually being there. – Rex Kerr Jan 17 '16 at 20:54