I'd like to understand this line from Java Concurrency in Practice -
Out-of-thin-air safety is the safety guarantee that when a thread reads a variable without synchronization it may see a stale value, but the value will be one set by a thread, not some random value.
What's meant by the value being random? How could it get into that state? An example in pseudocode would be very helpful.
Both of the existing answers describe tearing of reads and writes, which in my mind is not quite the same thing as out-of-thin-air values.
Tearing basically means that the computer rewrites
int g = 0x1234;
void threadA() {
g = 0xABCD;
}
void threadB() {
int local = g;
}
into
int g = 0x1234;
void threadA() {
g = 0xAB00; // "tearing"
g += 0x00CD;
}
void threadB() {
int local = g; // may read 0xAB00, which threadA never stored
}
(Depending on the instruction set, you might imagine seeing 0xAB34, 0x12CD, 0x00CD, 0x00AB, etc.)
However, out-of-thin-air (in my limited experience) usually means that the compiler rewrites
int g1 = 0x1234;
bool g2 = true;
void threadA() {
g1 = (g2 ? 0xABCD : 0x5678); // since g2 is true, this stores 0xABCD
}
void threadB() {
int local = g1;
}
into
int g1 = 0x1234;
bool g2 = false;
void threadA() {
g1 = 0x5678;
if (g2) g1 = 0xABCD;
}
void threadB() {
int local = g1; // may read 0x5678, which threadA never stored
}
This is closely related to "speculative execution" (which I think of as more of a hardware term), and it does not depend on tearing of reads or writes.
Also see my answer to this duplicate question (https://stackoverflow.com/a/26500593/1424877).
The best example I can think of is writing a long
in a 32 bit CPU.
According to the Java standard, long
has to be 64 bit wide. A 32 bit CPU cannot write a long
value in one atomic write, it has to write two 32 bit integers in order to achieve that.
Assuming A thread wrote only the first 32 bit value, then, the OS scheduled another (Java) thread on the same core that tries to read that semi-updated value - it will see random value. the quote you gave basically says "that cannot happen".
In Java, a variable is initialized with a "known" value when it is created (either a default, "zero-like" value, or the provided value, in aint value = 5;
situation).
The default initialization is described in section 4.12.5 of the spec.
What that says is that the variables are initialized with, effectively, "zero" (as interpreted for that type).
Now, generally, assignments to the variable are atomic. That is, they happen "at once", the value of the variable changes from one value to the new one.
The exceptions to that could appear for variables of type long
and double
. This is described in the spec.
Effectively, those values are 64 bit wide and could be set in two steps, one for each 32 bit half.
Together, the requirements above mean that the value of a variable could be in one of these states:
Quite deterministic and, arguably, no value could be read that comes out of thin air.
This is in contrast to languages such as C or C++ which don't always initialize the variables. So, you could read some random data (whatever was previously at that memory location).
Out of thin air values are values that are caused by a causal loop in the actions.
E.g.
int a=0
int b=0
Thread1:
r1=a (1)
b=r1 (2)
Thread2:
r2=b (3)
a=r2 (4)
Could it be that we end up with r1=42 and r2=42? This sound like insanity since the value 42 is never written, so how can we see these values.
Imagine that the CPU running thread 2 would speculate that b=42 at (3). This would cause r2=42 and a=42 (4). And this would cause r1=42 (1) and b=42 (2). The CPU running thread 2 is now very happy because speculation has worked because the value it speculated is the actual value that is read!
The JVM disallows such self justification. The formal part of the JMM contains 2 parts:
First part: Consistency: each read sees:
Second part: Causality
Consistency alone doesn't protect against such speculative self justification and therefor it is required that there should be at least 1 causal order that explain the execution. This causal order is a partial order and as a consequence forbids any executions that have a cycle in the causal order like the one above.
Out of thin air is not related to 64 bit longs/doubles on a 32 bit JVM. That problem is called torn reads/writes.
For more information see JSR-133 Java Memory Model and Thread Specification page 19.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With