javaconcurrencyjava-threadsjava-memory-model

How to use volatile to ensure sequential consistency


Consider two threads:

A==B==0 (initially)

Thread 1 Thread 2
B=42; if (A==1)
A=1; ...print(B)

To my knowledge if (at least) A is volatile we will only be able to read B==42 at the print. Though if only mark B as volatile we can read B==42 but also B==0.

I want to look at the case where only B is volatile more closely and understand why we can read B==0 based on what these docs say. To do so I started by adding all program order edges and synchronizes with as described in the docs:

enter image description here

The two edges from B=42 to A=1 are simple program order (PO) edges the rest are synchronizes with (SW) edges. According to the docs we have a SW edge when "The write of the default value [...] to each variable synchronizes-with the first action in every thread." (those are the first 4 edges in the picture) and "A write to a volatile variable v [...] synchronizes-with all subsequent reads of v" (the edges from B=42 to print(B)).

Now we can take a look at what happens before edges exists (HB), according to the docs each of these edges is also an HB ordering. Additionally for all hb(x,y) and hb(y,z) we have hb(x,z) (these edges are missing but we will still use those).

Finally, we get from the docs what we can read at print(B) from: "We say that a read r of a variable v is allowed to observe a write w to v if, in the happens-before partial order [...]:

Let's see if we can observe a write w (B=0) at a read r (print(B)). We indeed have not hb(r, w). However, we do have a write w' (B=42) intervening with hb(wow') and hb(w',r).

This makes me wonder can we observe B==0 at the print and if yes where is my reasoning or understanding of the docs wrong? I would like a answer that is clearly referring to the docs.

(I have looked at this post however I hope for an explanation referencing the JMM docs more closely, my question also arises from this particular code)


Solution

  • Just to make sure I understand you correctly:

    We are interested in the execution where:

    In terms of the JMM actions the execution is this (for brevity only actions on A and B are shown):

    Initially:
             write(A=0)
    volatile-write(B=0)
    
    Thread1:                Thread2:
    volatile-write(B=42)             read(A):1
             write(A=1)     volatile-read(B):0
    

    Here are program-order and synchronizes-with ([po] and [sw] on the diagram) relations between the actions in the execution:

             write(A=0)                         
                ↓[po]                           
    volatile-write(B=0)                         
                ││└──────────────────────┐      
                │└───────────────┐       │      
                ↓[sw]            │       ↓[sw]  
    volatile-write(B=42)         │     read(A):1
                ↓[po]            ↓[sw]   ↓[po]  
             write(A=1)       volatile-read(B):0
    

    Notes:

    And here are happens-before relations (built from [po] and [sw]) between the actions:

             write(A=0)                         
                ↓[hb]                           
    volatile-write(B=0)                         
                │└──────────────────────┐       
                ↓[hb]                   ↓[hb]   
    volatile-write(B=42)              read(A):1 
                ↓[hb]                   ↓[hb]   
             write(A=1)       volatile-read(B):0
    

    Happens-before consistency according to the JLS:

    We say that a read r of a variable v is allowed to observe a write w to v if, in the happens-before partial order of the execution trace:

    r is not ordered before w (i.e., it is not the case that hb(r, w)), and

    there is no intervening write w' to v (i.e. no write w' to v such that hb(w, w') and hb(w', r)).

    Informally, a read r is allowed to see the result of a write w if there is no happens-before ordering to prevent that read.

    A set of actions A is happens-before consistent if for all reads r in A, where W(r) is the write action seen by r, it is not the case that either hb(r, W(r)) or that there exists a write w in A such that w.v = r.v and hb(W(r), w) and hb(w, r).

    In a happens-before consistent set of actions, each read sees a write that it is allowed to see by the happens-before ordering.

    Happens-before consistency isn't violated for read(A):1 because, as you can see in the diagram above, there is no happens-before relations between write(A=1) and read(A):1.

    volatile-read(B):0 is also fine (it's explained above).

    In fact, I don't see anything in the JMM that is violated in this execution - so IMO the execution is legal according to the JMM.