"But is the assignment of a real value to the lazy val thread-safe?"
Not to be pedantic, but I would re-phrase that sentence as, "the evaluation of a lazy val to a real value".
I would have also thought that it might be evaluated twice.
However examining the bytecode indicated otherwise.
Using the Intellij ASM Bytecode outline plugin (kudos to it's creator), I decompiled the following object:
object Lazy {
import scala.util.Random
val bar = "hello " + Random.nextLong() + " world "
}
The code where the field is accessed decompiles to this:
// access flags 0x1
public bar()Ljava/lang/String;
ALOAD 0
GETFIELD Lazy$.bitmap$0 : Z
IFEQ L0
ALOAD 0
GETFIELD Lazy$.bar : Ljava/lang/String;
GOTO L1
L0
FRAME SAME
ALOAD 0
INVOKESPECIAL Lazy$.bar$lzycompute ()Ljava/lang/String;
OK, so no synchronization there. It just checks if it's null, then calls lzycompute if necessary.
But, looking at the lzycompute implementation...
private bar$lzycompute()Ljava/lang/String;
TRYCATCHBLOCK L0 L1 L2 null
ALOAD 0
DUP
ASTORE 1
MONITORENTER
L0
ALOAD 0
GETFIELD Lazy$.bitmap$0 : Z
IFNE L3
ALOAD 0
NEW scala/collection/mutable/StringBuilder
DUP
INVOKESPECIAL scala/collection/mutable/StringBuilder.<init> ()V
LDC "hello "
INVOKEVIRTUAL scala/collection/mutable/StringBuilder.append (Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
We see that the string concatenation operations are run inside a synchronised try/catch block.
So at least in this case, evaluation of the expression is thread safe, but it may be evaluated multiple times.
Hope this helps,
Bryan