In-depth understanding of the Java Memory Model (six) - final

Compared with the previously described lock and volatile, read and write the final fields like ordinary variable access. For the final domain, compilers and processors to comply with two reordering rules:

  1. Written in the constructor of a final field, and then the object referenced this configuration assigned to a reference variable, not reordering between these two operations.
  2. Reading a first target domain comprising a final reference, and then read the final initial field, the reordering is not between these two operations.

Below, we pass some example code to illustrate these two rules:

public class FinalExample {
    int i;                            // 普通变量 
    final int j;                      //final 变量 
    static FinalExample obj;

    public void FinalExample () {     // 构造函数 
        i = 1;                        // 写普通域 
        j = 2;                        // 写 final 域 
    }

    public static void writer () {    // 写线程 A 执行 
        obj = new FinalExample ();
    }

    public static void reader () {       // 读线程 B 执行 
        FinalExample object = obj;       // 读对象引用 
        int a = object.i;                // 读普通域 
        int b = object.j;                // 读 final 域 
    }
}

It is assumed that a writer thread A executes () method, followed by another thread B executed Reader () method. Below we interact through these two threads to illustrate these two rules.

Write reordering rules final domain

Written final field reordering rules prohibit write reordering final field outside the constructor. This implementation rule contains the following two aspects:

  • JMM prohibits the compiler to write reordering final field outside the constructor.
  • Before the compiler will write after the final field, constructors return, insert a StoreStore barrier. This barrier is prohibited processor to write reordering final field outside the constructor.

Let us now analyze writer () method. writer () method includes only one line of code: finalExample = new FinalExample (). This code includes two steps:

  1. FinalExample constructed object type;
  2. The assignment of this reference object to the reference variable obj.

Thread B reads assume no object reference reordering and read between the members of the domain object (immediately explain why this assumption), below is a possible execution sequence:

In the figure, the write operations are common domain discouraged ordered to compile a constructor outside thread B reads the value of the plain erroneous readings before initialization of the variable i. Written final field operation is re-written final collation domain "is defined" within the constructor, the thread B reads the correct read value after final variable initialization.

Written final field reordering rules to ensure that: before any thread can be seen as a reference, final domain object has already been initialized correctly, while the ordinary domain does not have this guarantee in the object. FIG above as an example, in the reader thread B "see" when the object reference obj, probably not yet been constructed object obj (i write operation to the common domain to be reordered to constructors, when the initial value is not written yet 2 Common domains i).

Read the final rule reordering domain

Reordering the read field final rules are as follows:

  • In one thread, the primary object reference with the read initial read of the final field contains objects, the processor is prohibited JMM reordering two operations (Note that this rule only for the processor). LoadLoad compiler inserts a barrier in front of the final read-domain operations.

First reading and the initial reading final object reference field contains the object, an indirect dependency between the two operations. Since the compiler compliance indirect dependencies, the compiler does a reordering of these two operations. Most processors will abide indirectly dependent, most processors will not reorder these two operations. However, a few of the processor allows the operator to make an indirect dependence reordering (such as alpha processor), this rule is designed to for such processors.

Reader () method includes three operations:

  1. First read reference variable obj;
  2. Initial reference to read ordinary domain variable j obj points to an object.
  3. First read reference variable pointing object obj final fields i.

Now we assume that the writer thread A does not have any reordering occurs, while in non-compliance with program execution indirectly dependent on the processor, the following is a possible execution sequence:

In the figure, the read operation of an ordinary object domain by processor reorder references to objects before reading. Read ordinary domain, which has not been written thread A written, this is a wrong read operation. Final fields read rules reordering operation target final fields will read "defined" After reading object reference, at which time the final field has been initialized the thread A, which is a correct read operation.

Reading final field reordering rules to ensure that: before the final reading of an object domain, we will first read the final field contains an object reference. In this sample program, if the reference is not null, then the object reference final domain must have been initialized A thread before.

If the final field is a reference type

Above we see the final field is the underlying data type, let's see if the final field is a reference type, will have what effect?

Consider the following sample code:

public class FinalReferenceExample {
final int[] intArray;                     //final 是引用类型 
static FinalReferenceExample obj;

public FinalReferenceExample () {        // 构造函数 
    intArray = new int[1];              //1
    intArray[0] = 1;                   //2
}

public static void writerOne () {          // 写线程 A 执行 
    obj = new FinalReferenceExample ();  //3
}

public static void writerTwo () {          // 写线程 B 执行 
    obj.intArray[0] = 2;                 //4
}

public static void reader () {              // 读线程 C 执行 
    if (obj != null) {                    //5
        int temp1 = obj.intArray[0];       //6
    }
}
}

Here final field is a reference type, that references an int type array object. For reference types, the write field final reordering rules compiler and processor adds the following constraint:

  1. Written in the object constructor function of the final reference member of a domain, and then this is referenced in configuration outside the object constructor function assigned to a reference variable, not reordering between these two operations.

The above procedure of example, we assume that the first thread A executed writerOne () method is executed after the thread B executed writerTwo () method is executed after the thread C executed Reader () method. Here is one possible thread execution timing:

In the figure, 1 is written on the final field, 2 is written into the final member of a domain of the domain object referenced, 3 is the structure of the object reference is assigned to a reference variable. Here in addition to 1 and 3 can not reorder the aforementioned, 2 and 3 can not be reordered.

JMM ensures that the reader thread C at least be able to see members of the domain write write thread A reference to the object in the constructor for the final. I.e. 0 C to see at least in the array is labeled 1. The write thread B to an array element is written, the reader thread might look to C, you may not see. JMM is not guaranteed to read the writing thread B thread C visible because there is competition between data read and write thread B thread C, at this time the results are unpredictable.

If you want to make sure that read write thread B thread C see the array elements to write, write, requires the use of synchronization primitives (lock or volatile) between threads B and C to ensure that the memory read threads visibility.

Why final quote can not "escape" from inside the constructor

As mentioned earlier, written final field reordering rules to ensure that: before any thread is visible in the reference variables that point to the final reference variable domain object has been properly initialized in the constructor. In fact, to get this effect, we also need a guarantee: within the constructor can not let this be a reference to the constructed object visible to other threads, that is, object references can not "escape" in the constructor. To illustrate the problem, let's look at the sample code below:

public class FinalReferenceEscapeExample {
final int i;
static FinalReferenceEscapeExample obj;

public FinalReferenceEscapeExample () {
    i = 1;                              //1 写 final 域 
    obj = this;                          //2 this 引用在此“逸出”
}

public static void writer() {
    new FinalReferenceEscapeExample ();
}

public static void reader {
    if (obj != null) {                     //3
        int temp = obj.i;                 //4
    }
}
}

 Suppose thread A executes a writer () method, another thread B executed Reader () method. 2 so that the operation object is here configured as a visible before thread B has not been completed. 2 even if the operation is the last step where the constructor, even if the operator 2 and a rear row operation, performing read () method of the thread still may not see the value of the final field is initialized, because the operation in the program where a operation between 2 and may be reordered. The actual timing may be performed as shown below:

From the figure we can see that: before the constructor returns the referenced object is not visible to other threads configured, the final because the domain has not been initialized. After the constructor returns, any thread will be guaranteed to see the value after the final field initialized correctly.

Semantic final implemented in a processor

Now we x86 processor, for example, specific semantics to achieve final processor.

We mentioned above, write final field reordering rules will be asked to interpret ed in writing after the final field, constructor return before inserting a StoreStore barrier screen. Read field final reordering rules require the compiler inserts a LoadLoad final barrier in front of the read operation domain.

Because x86 processors will not write - write reordering do so in x86 processors, write final field needed StoreStore barrier screen will be omitted. Also, since the operation of the x86 processor is not an indirect dependencies do reordering, so x86 processor, the read barrier LoadLoad final fields will need to be omitted. That is the x86 processor, the final reading of the domain / write does not insert any memory barrier!

Why JSR-133 to enhance the semantic final

In the old Java memory model, the most serious flaw is a thread might see the value will change final fields. For instance, when a thread is currently seeing a value of 0 (the default value before yet initialized) a final field of plastic surgery, after a period of time to go read this thread final value of this field, but found that the value becomes 1 (being a value after the thread initialization). The most common example is the old Java memory model, the value of String may change (Ref. 2, there is a concrete example, the interested reader can refer to themselves, not go into details here).

To fix this vulnerability, JSR-133 expert group enhances the semantic final. By increasing the field for the final read-write and re-ordering rules, initialization can provide security for java programmers: As long as the object is properly constructed in (cited no "escape" in the constructor constructed object), you do not need to use synchronous (refers to the use of lock and volatile), you can ensure that any thread can see the value after the final field is initialized in the constructor.

Published 136 original articles · won praise 6 · views 1477

Guess you like

Origin blog.csdn.net/weixin_42073629/article/details/104741742