Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is out-of-thin-air safety?

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.

like image 386
Julian A. Avatar asked Mar 03 '17 20:03

Julian A.


4 Answers

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).

like image 74
Quuxplusone Avatar answered Oct 19 '22 22:10

Quuxplusone


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".

like image 38
David Haim Avatar answered Oct 19 '22 22:10

David Haim


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:

  1. the default value ("zero"/null)
  2. the value set by a thread
  3. for non-volatile long or doubles, half of the desired value (either 0|half or half|0)

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).

like image 28
Andrei Avatar answered Oct 19 '22 21:10

Andrei


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:

  • either the most recent write before it in the happens before order
  • or a write whereby the read/write are not ordered by the happens before (needed to deal with data races).

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.

like image 38
pveentjer Avatar answered Oct 19 '22 22:10

pveentjer