Java concurrent programming principle and Combat 44: final domain memory semantics

Source: Internet
Author: User
Tags volatile

I. Final field reordering rules

For final domains, the compiler and processor follow the two re-order rules:
1. Writing to a final field within the constructor, and then assigning a reference to the constructed object to a reference variable, cannot be reordered between the two operations.

2. The first time you read an application that contains the final domain object, and the final field is then first read, the two actions cannot be reordered

These two rules are described separately by an example:

public class Finaltest {    int i;//normal variable    final int J;    static Finaltest obj;    Public Finaltest () {        i = 1;        j = 2;    }    public static void writer () {        obj = new Finaltest ();    }    public static void Reader () {        finaltest tets = obj;//Read object reference        int a = TETS.I;        int b = TETS.J;    }}

This assumes that a thread a executes the writer method, and then another thread B executes the reader method. We illustrate these two rules through the interaction of these two threads.

  A reorder rule that writes a final field prevents the final field from being sorted out of the constructor . The implementation of this rule consists of two aspects:

1.JMM prevents the compiler from ordering the final field's write-back to the constructor

2. The compiler inserts a storestore barrier before the constructor return after the final field is written. This barrier prevents the processor from sorting the final field's write-back to the constructor

Now let's analyze the writer method, the writer method contains only one line of code, obj = new Finaltest (); This line of code contains two steps:
1. Constructing an object of type Finaltest

2. Assign a reference to the object to obj

Suppose there is no reordering between the Read object reference of thread B and the member domain of the Read object, which is a possible execution timing

  

In, the operations that write the normal domain are sorted out of the constructor by the compiler, and read thread B incorrectly reads the value before the normal variable i is initialized. And the final field is written to the final field reordering rules are limited to the constructor, read thread B correctly read to the final variable after the initialization of the value.

The reordering of final fields ensures that the final domain of the object has been initialized before the object reference is visible to any thread, and the normal variable does not have this guarantee. For example, when read thread B sees the object obj, it is likely that the Obj object has not been constructed (the write to normal domain i is reordered to the constructor, at which point the initial value 1 is not written to the normal domain i)

The reordering rule for a read final field is: In one thread, the reference to the first read object and the final domain contained in the first read of this object, JMM suppresses the reordering of the two operations (the rule is for processors only). The compiler adds a loadload barrier before the operation of the final domain.

An indirect dependency exists between the first-read object reference and the first-read final domain that the object contains. Because the compiler adheres to indirect dependencies, the compiler does not reorder the two operations. Most processors also follow indirect dependencies and do not reorder the two operations. But there are a handful of processors that allow reordering of operations that have indirect dependencies (such as an Alpha processor), which is specifically intended for this processor.

In the above example, the reader method contains three operations

1. First read reference variable obj

2. Initial read reference variable points to the normal domain of the object

3. First read reference variable points to the final field of the object

It is now assumed that write thread A does not have any reordering, and that the program executes on a processor that does not comply with indirect dependencies, which is a possible execution sequence:

  

In, the normal domain operation of the Read object is reordered by the processor before the Read object reference. When reading a normal domain, the domain has not been written by the write thread, which is an incorrect read operation, and the re-collation of the Read final field "qualifies" the operation of the final domain of the Read object after the Read object reference, at which point the final domain has been initialized by a thread, which is a correct read operation.

Reading the collation of the final field ensures that a reference to the object containing the final field must be read before the final field of an object is read. In this example program, if the reference is not NULL, then the final domain of the referencing object must have been initialized by a thread.

Two. Final fields are reference types

The final fields above are basic types, and if the final field is a reference type, what effect will it have?

  

public class Finalreferencetest {    final int[] arrs;//final references    static finalreferencetest obj;    Public Finalreferencetest () {        arrs = new INT[1];//1        arrs[0] = 1;//2    } public    static void Write0 () {//a thread C7/>obj = new Finalreferencetest ();//3    } public    static void Write1 () {//thread B        obj.arrs[0] = 2;//4    } Public    static void Reader () {//c thread        if (obj!=null) {//5            int temp =OBJ.ARRS[0];//6        }    }}

In the example above, the final field is a reference type that references an array of type int, and for reference types, the collation of the Write final field adds a bit of constraint to the compiler and processor: write to the member domain of a final referenced object within the constructor, With the subsequent assignment of a reference to a reference variable outside of the constructor, there is no reordering between the two operations.

In this example, assuming that thread a performs the write0 operation first, thread B performs a write1 operation after execution, and thread C performs a reader operation after execution is a possible execution timing:
  

In:, 1 is the write to the final field, 2 is the write to the member domain of the object referenced by this final field, and 3 is the assignment of the referenced object's reference to a reference variable. In addition to the previously mentioned 1 cannot be and 3 reorder, 2 and 3 can not be re-ordered.

JMM ensures that read thread C can at least see write thread a writes to the member domain of the final reference object in the constructor. That is, C can see at least the value of the array subscript 0 is 1. While writing thread B writes to array elements, read thread C may or may not be visible. JMM does not guarantee that the write to thread B is visible to read thread C because there is data contention between write thread B and read thread C, where the execution result is unpredictable.

If you want to make sure that read thread C sees write thread B write to the array element, write thread B and read thread C need to use synchronization primitives (lock or volatile) to ensure memory visibility.

As we mentioned earlier, the collation of the final field ensures that the final domain of the object to which the reference variable points is already properly initialized in the constructor before the reference variable is visible to any thread. In fact, to get this effect, you also need a guarantee: Inside the constructor, you cannot let the reference of the constructed object be seen by other threads, that is, the object reference cannot "escape" in the constructor.

  

public class Finalreferenceescapeexample {final int i;  static Finalreferenceescapeexample obj; Public Finalreferenceescapeexample () {i = 1;//1 Write final field obj = this,//2 This reference in this "escape"} public static void writ  ER () {new finalreferenceescapeexample (); public static void Reader () {if (obj! = null) {///3 int temp = OBJ.I;//4}}}

Suppose one thread a executes the writer () method and another thread B executes the reader () method. Operation 2 here makes it visible to thread B before the object is constructed. Even if Operation 2 here is the last step of the constructor, and after action 2 in the program is followed by operation 1, the thread that executes the read () method may still not be able to see the value after the final domain is initialized, because there may be reordering between operations 1 and 2. The actual execution timing may be as follows:

  

It can be seen that the reference to the constructed object cannot be visible to other threads until the constructor returns. Because the final domain may not have been initialized at this time: After the constructor returns, any thread will be guaranteed to see the values after the final domain is initialized correctly.

Three. Implementation of final semantics in the processor

Now we take the X86 processor as an example to illustrate the concrete implementation of final semantics in the processor.

As we mentioned above, the reordering rules that write final fields require the compiler to insert a storestore screen before the constructor return after the final field is written. The re-collation of the Read final field requires the compiler to insert a loadload barrier before the operation of the final domain. Because the X86 processor does not reorder write-write operations, the Storestore screen that is required to write the final domain in the X86 processor is omitted. Similarly, because the X86 processor does not reorder operations that have indirect dependencies, the loadload barriers required to read the final domain are omitted from the X86 processor. In other words, the X86 processor
, the final domain read/write does not insert any memory barriers.

One of the most serious flaws in the old Java memory model is that the thread might see the final domain value change. For example, a thread currently sees a value of 0 for an integer final field (before the default value has not been initialized), and after a period of time the thread then reads the value of the final field, but finds that the value becomes 1 (the value after the initialization of a thread). The most common example of this is that the value of string may change in the old Java memory model. To remedy this vulnerability, the JSR-133 Expert Group Enhanced final semantics. By adding write and read reordering rules for final fields, you can provide the Java programmer with an initial security guarantee: As long as the object is properly constructed (the reference to the constructed object does not "escape" in the constructor), It is not necessary to use synchronization (the use of lock and volatile) to ensure that any thread can see the value of the final field after it has been initialized in the constructor.

Resources:

78738197

80385520

Http://www.itdaan.com/index.html

Java concurrent programming principle and Combat 44: final domain memory semantics

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.