The art of Java Concurrency Programming reading notes-Chapter III Java memory Model (ii)

Source: Internet
Author: User
Tags volatile

An overview

This article belongs to "Java Concurrent Programming Art" Reading Notes series, chapter three Java memory model the second part.

The memory semantics of the second final

Final is a reserved keyword in Java and can declare member variables, methods, classes, and local variables. You can refer to the previously sorted keyword final. Here the author mainly introduces the memory semantics of the final domain.

For the final domain, the compiler and processor are subject to two reordering 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 a reference to an object that contains a final field, and the final field is then first read, the two operations cannot be reordered.
Below, we illustrate these two rules separately with some exemplary code:

public class Finalexample {    int i;                            Normal variable    final int J;                      Final variable    static finalexample obj;    public void Finalexample () {     //constructor        i = 1;                        Write normal domain        j = 2;                        Write final domain    } public    static void writer () {    //write thread a executes        obj = new Finalexample ();    }    public static void Reader () {       //Read thread B executes        finalexample object = obj;       Read object reference        int a = OBJECT.I;                Read normal domain        int b = OBJECT.J;                Read final field    }}
This assumes that a thread a executes the writer () method, and then another thread B executes the reader () method. Below we illustrate these two rules through the interaction of these two threads.

2.1 Write the reorder rules for the final field

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 the following 2 aspects:

    • JMM prevents the compiler from ordering the final field's write-back to the constructor.
    • The compiler inserts a storestore barrier before the constructor return after the final field is written. This barrier prohibits the processor from ordering the final domain's write-back to the constructor.

Now let's analyze the writer () method. The writer () method contains only one line of code: Finalexample = new Finalexample (). This line of code contains two steps:

    1. Constructs an object of type finalexample;
    2. Assigns a reference to this object to the reference variable, obj.

Assuming there is no reordering between the thread B-Read object reference and the member domain of the Read object (which immediately explains why this assumption is required), it is a possible execution sequence:


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

2.2 Read the Reorder rules for final fields

The reordering rules for the Read final field are as follows:

    • In one thread, the first read object reference and the first read of the object contains the final domain, JMM prohibit the processor reordering these two operations (note that this rule is only for the processor). The compiler inserts a loadload barrier before the read final domain operation.

The reader () method consists of three actions:

    1. First read reference variable obj;
    2. The first read reference variable, obj, points to the normal domain J of the object.
    3. The first read reference variable, obj, points to the final field I of the object.
Now let's assume that write thread A does not have any reordering, and the program executes on a processor that does not obey the indirect dependencies, here is a possible execution timing:

In, the operation of the normal domain 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 write thread A, which is an incorrect read operation. The re-collation of the final field will "qualify" the operation of the final field of the Read object after the Read object reference, and the final domain has been initialized by a thread, which is the 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.

2.3final domain is a reference type polygon the final field we see is the underlying data type, let's see if the final field is a reference type.
public class Finalreferenceexample {final int[] intarray;                     Final is a reference type static finalreferenceexample obj;public finalreferenceexample () {        //constructor    Intarray = new int[1];              1    intarray[0] = 1;                   2}public static void Writerone () {          //write thread a executes    obj = new Finalreferenceexample ();  3}public static void Writertwo () {          //write thread B execution    obj.intarray[0] = 2;                 4}public static void Reader () {              //Read thread C executes    if (obj! = null) {                    //5        int temp1 = obj.intarray[0];       6    }}}

Here the final field is a reference type that references an array object of type int. For reference types, the reorder rules for the write final domain add the following constraints to the compiler and processor:

    1. Writes to a member field of a final referenced object within a constructor, and then assigns a reference to a reference variable outside of the constructor to the constructed object, and cannot be reordered between the two operations.

For the above example program, we assume that thread a executes the Writerone () method first, and thread B executes the Writertwo () method after execution, after the thread C executes the reader () method. The following is a possible sequence of thread execution:



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 a reference to a reference variable to the referenced object. 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 an array element, read thread C may or may not be seen. 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 visible to other threads, that is, the object reference cannot be "escaped" in the constructor.
Implementation of 2.4final 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 for final fields require the translator 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. That is, in the x86 processor, the final domain read/write does not insert any memory barrier!

Three double check locking and optimization here the author introduces a double check lock, which is actually a singleton pattern, referring to the singleton pattern I've previously collated. The Java language Specification specifies that for each class or interface C, there is a unique initialization lock LC corresponding to it. The mapping from C to LC is implemented freely by the specific implementation of the JVM. The JVM acquires this initialization lock during class initialization, and each thread acquires at least one lock to ensure that the class has been initialized. The author also describes the process of class initialization, which can be referred to in the original book 3.8.4 chapter no longer posted here. Four Java Memory models review this section of the partial theory, where the main principles are extracted. 4.1 Processor memory model sequential consistency The memory model is a theoretical reference model, and the JMM and processor memory models typically refer to sequential consistent memory models at design time. The JMM and processor memory models are designed to relax the sequential consistency model, because if the processor and the JMM are implemented exactly as sequential conformance models, many processor and compiler optimizations are banned, which can have a significant impact on performance.
Because the common processor memory model is weaker than JMM, the Java compiler inserts a memory barrier at the appropriate location in the execution instruction sequence to limit processor reordering when generating bytecode. JMM masks differences in the different processor memory models, which present a consistent memory model for Java programmers on different processor platforms.

4.2JMM is designed to provide sufficient memory visibility for programmers, on the other hand, the limitations of compilers and processors need to be as relaxed as possible and demand balanced.

JMM the reordering of happens-before requirements is divided into the following two categories:

    • Will change the reordering of the results of the program execution.
    • Does not change the reordering of the results of the program execution.

JMM has adopted different strategies for reordering these two different properties:

    • For reordering that alters the execution of the program, JMM requires that the compiler and processor must disallow this reordering.
    • JMM does not require the compiler and processor to reorder the results of the program execution (JMM allows this reordering).

Here is the design of JMM:


It can be seen from two points:

    • JMM provides programmers with the Happens-before rules to meet the needs of programmers. JMM's Happens-before rules are easy to understand and provide programmers with sufficient memory visibility guarantees (some memory visibility guarantees are not necessarily real, such as a happens-before B above).
    • JMM the compiler and processor are as small as possible. From the above analysis, we can see that JMM is actually following a basic principle: as long as the execution of the program does not change the results (refers to the single-threaded programs and the correct synchronization of multithreaded procedures), compiler and processor how to optimize the line. For example, if the compiler after careful analysis, that a lock will only be accessed by a single thread, then this lock can be eliminated. For example, if the compiler after careful analysis, that a volatile variable will only be accessed by a single thread, then the compiler can treat this volatile variable as a common variable. These optimizations will not change the execution result of the program, but also can improve the execution efficiency of the program.
4.3JMM of memory Visibility guaranteed

The memory visibility of Java programs is guaranteed to be divided into the following three categories by program type:

    1. Single threaded. There is no memory visibility issue with single threaded threads. The compiler, runtime, and processor will work together to ensure that the results of a single-threaded program are the same as the execution of the procedure in the sequential consistency model.
    2. A multithreaded program that synchronizes correctly. The execution of multithreaded programs that are correctly synchronized will have sequential consistency (the execution result of the program is the same as that of the program in the sequential-consistent memory model). This is the focus of JMM attention, JMM provides the programmer with a memory visibility guarantee by restricting the reordering of compilers and processors.
    3. multithreaded programs that are not synchronized/not synchronized correctly. JMM provides them with minimal security: The value read when the thread executes, either the value written by a previous thread or the default value (0,null,false).

Shows the similarities and differences between the three types of programs in JMM and the execution results in the sequential consistent memory model:


As long as multithreaded programs are synchronized correctly, JMM guarantees that the program executes on any processor platform, consistent with the execution of the program in the sequential consistent memory model.

4.4jsr-133 patching of old memory models

JSR-133 's patching of the old memory model before JDK5 is mainly two:

    • Enhance the memory semantics of volatile. The old memory model allows volatile variables to be re-ordered with ordinary variables. JSR-133 strictly restricts the reordering of volatile variables and ordinary variables, so that volatile write-read and lock-release-fetches have the same memory semantics.
    • Enhances final memory semantics. In the old memory model, the value of reading the same final variable multiple times may be different. To do this, JSR-133 added two reordering rules for final. Now, final has initialization security.

The art of Java Concurrency Programming reading notes-Chapter III Java memory Model (ii)

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.