Immutability and reordering

后端 未结 10 680
星月不相逢
星月不相逢 2020-12-04 10:07

The code below (Java Concurrency in Practice listing 16.3) is not thread safe for obvious reasons:

public class UnsafeLazyInitialization {
    private static         


        
10条回答
  •  误落风尘
    2020-12-04 10:53

    UnsafeLazyInitialization.getInstance() can never return null.

    I'll use @assylias's table.

                                  Some Thread
    ---------------------------------------------------------------------
     10: resource = null; //default value                                  //write
    =====================================================================
               Thread 1               |          Thread 2                
    ----------------------------------+----------------------------------
     11: a = resource;                | 21: x = resource;                  //read
     12: if (a == null)               | 22: if (x == null)               
     13:   resource = new Resource(); | 23:   resource = new Resource();   //write
     14: b = resource;                | 24: y = resource;                  //read
     15: return b;                    | 25: return y;    
    

    I'll use the line numbers for Thread 1. Thread 1 sees the write on 10 before the read on 11, and the read on line 11 before the read on 14. These are intra-thread happens-before relationships and don't say anything about Thread 2. The read on line 14 returns a value defined by the JMM. Depending on the timing, it may be the Resource created on line 13, or it may be any value written by Thread 2. But that write has to happen-after the read on line 11. There is only one such write, the unsafe publish on line 23. The write to null on line 10 is not in scope because it happened before line 11 due to intra-thread ordering.

    It doesn't matter if Resource is immutable or not. Most of the discussion so far has focused on inter-thread action where immutability would be relevant, but the reordering that would allow this method to return null is forbidden by intra-thread rules. The relevant section of the spec is JLS 17.4.7.

    For each thread t, the actions performed by t in A are the same as would be generated by that thread in program-order in isolation, with each write w writing the value V(w), given that each read r sees the value V(W(r)). Values seen by each read are determined by the memory model. The program order given must reflect the program order in which the actions would be performed according to the intra-thread semantics of P.

    This basically means that while reads and writes may be reordered, reads and writes to the same variable have to appear like they happen in order to the Thread that executes the reads and writes.

    There's only a single write of null (on line 10). Either Thread can see its own copy of resource or the other Thread's, but it cannot see the earlier write to null after it reads either Resource.

    As a side note, the initialization to null takes place in a separate thread. The section on Safe Publication in JCIP states:

    Static initializers are executed by the JVM at class initialization time; because of internal synchronization in the JVM, this mechanism is guaranteed to safely publish any objects initialized in this way [JLS 12.4.2].

    It may be worth trying to write a test that gets UnsafeLazyInitialization.getInstance() to return null, and that gets some of the proposed equivalent rewrites to return null. You'll see that they're not truly equivalent.

    EDIT

    Here's an example that separates reads and writes for clarity. Let's say there's a public static variable object.

    public static Object object = new Integer(0);
    

    Thread 1 writes to that object:

    object = new Integer(1);
    object = new Integer(2);
    object = new Integer(3);
    

    Thread 2 reads that object:

    System.out.println(object);
    System.out.println(object);
    System.out.println(object);
    

    Without any form of synchronization providing inter-thread happens-before relationships, Thread 2 can print out lots of different things.

    1, 2, 3
    0, 0, 0
    3, 3, 3
    1, 1, 3
    etc.
    

    But it cannot print out a decreasing sequence like 3, 2, 1. The intra-thread semantics specified in 17.4.7 severely limit reordering here. If instead of using object three times we changed the example to use three separate static variables, many more outputs would be possible because there would be no restrictions on reordering.

提交回复
热议问题