Java Memory Model-final, java model-final
Compared with the locks and volatile described earlier, the read and write operations on the final domain are more like common variable access. For final domains, the compiler and processor must follow two reordering rules:
Below, we will illustrate these two rules through some exemplary code:
Public class FinalExample {int I; // common variable final int j; // final variable static FinalExample obj; public void FinalExample () {// constructor I = 1; // write common 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 the common domain int B = object. j; // read final domain }}
Assume that one thread A executes the writer () method, and the other thread B executes the reader () method. The following describes the two rules through the interaction between the two threads.
Rewrite Rules for final Fields
Re-sorting rules for writing final fields do not allow re-sorting of final fields beyond the constructor. The implementation of this rule includes the following two aspects:
- JMM prohibits the compiler from sorting the write duplicates of the final domain outside the constructor.
- The compiler inserts a StoreStore barrier after writing the final domain and before the constructor returns. This barrier prohibits the processor from sorting the write duplicates of the final domain outside the constructor.
Now let's analyze the writer () method. The writer () method only contains one line of code: finalExample = new FinalExample (). This line of code contains two steps:
Assuming that no re-sorting is performed between the read object reference of thread B and the member fields of the read object (this will immediately explain why this assumption is required), it is a possible execution sequence:
In, the operations to write common domains are reordered by the compiler to the constructor. The read thread B mistakenly reads the values before the initialization of the common variable I. The operation to write the final domain is "Limited" by the re-sorting rule for the write final domain within the constructor. The read thread B correctly reads the value after the final variable initialization.
The re-sorting rule for the final domain can ensure that the final domain of the object has been correctly initialized before the object reference is visible to any thread. For example, when the reading thread B "sees" that the object references obj, it is likely that the obj object has not been constructed completely (write operations on the common domain I are reordered to the constructor, at this time, the initial value 2 has not been written to the common domain I ).
Re-sorting rules for read final Domains
The reordering rules for read final fields are as follows:
- In a thread, JMM prohibits the operation of the final domain contained in the first read object reference and the first read object (note that this rule is only applicable to the processor ). The compiler inserts a LoadLoad barrier before the final domain operation.
There is an indirect dependency between the first read object reference and the final domain contained in the first read object. Since the compiler complies with indirect dependencies, the compiler does not reorder these two operations. Most processors also follow indirect dependencies, and most processors do not reorder these two operations. However, a few processors allow re-sorting (such as alpha processors) operations with indirect dependencies. This rule is specifically used for such processors.
The reader () method contains three operations:
Now we assume that the write thread A does not carry out any re-sorting, and the program is executed on A processor that does not comply with the indirect dependency. Below is A possible execution sequence:
In, operations on the common domain of the read object are sorted by the processor before the read object reference. When reading A common domain, this domain is not written by write thread A, which is an incorrect read operation. The re-sorting rule of the read final domain will restrict the operation of the read object's final domain after the read object is referenced. At this time, the final domain has been initialized by thread, this is a correct read operation.
The re-sorting rule for the read final domain ensures that before reading an object's final domain, it will read the reference of the object containing this final domain. In this example, if the reference is not null, the final domain of the referenced object must have been initialized by thread.
If the final domain is of the reference type
The final domain we see above is the basic data type. Let's see what the final domain will do if it is a reference type?
See the following sample code:
Public class FinalReferenceExample {final int [] intArray; // final is the 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 executes 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, which references an array object of the int type. For reference types, the re-sorting rules for writing final domains Add the following constraints to the compiler and processor:
In the preceding example, we assume that thread A executes the writerOne () method first, thread B executes the writerTwo () method after execution, and thread C executes the reader () method after execution. The following is a possible thread execution sequence:
In, 1 is the write to the final domain, 2 is the write to the member domain of the object referenced by the final domain, and 3 is to assign the reference value of the constructed object to a reference variable. In addition to the 1 and 3 mentioned above, 2 and 3 cannot be reordered.
JMM can ensure that the read thread C can at least see the write thread A writes to the member domain of the final referenced object in the constructor. That is, C can see at least the value of array subscript 0 is 1. The write thread B may or may not be able to see the reading thread C when writing array elements. JMM does not guarantee that the write of thread B is visible to the read thread C. Because data competition exists between the write thread B and the read thread C, the execution result is unpredictable at this time.
To ensure that the read thread C sees the write thread B writing to the array elements, use the synchronization primitive (lock or volatile) between the write thread B and the read thread C to ensure memory visibility.
Why can't final references "escape" from the constructor"
As mentioned above, the re-sorting rules for writing final domains can ensure that: Before the referenced variables are visible to any thread, the final domain of the object to which the referenced variable points has been correctly initialized in the constructor. To achieve this effect, we also need to ensure that the reference of the constructed object cannot be visible to other threads within the constructor, that is, the object reference cannot "escape" in the constructor ". To illustrate the problem, let's look at the following sample code:
Public class FinalReferenceEscapeExample {final int I; static FinalReferenceEscapeExample obj; public FinalReferenceEscapeExample () {I = 1; // 1 Write final domain obj = this; // 2 this reference "escape"} public static void writer () {new FinalReferenceEscapeExample ();} public static void reader {if (obj! = Null) {// 3 int temp = obj. I; // 4 }}}
Assume that one thread A executes the writer () method, and the other thread B executes the reader () method. Here, operation 2 makes the object visible to thread B before being constructed. Even if operation 2 is the last step of the constructor, and operation 2 is placed after operation 1 in the program, execute read () the thread of the method may still not be able to see the final Domain value after initialization, because operation 1 and operation 2 may be reordered here. The actual execution sequence may be shown in:
We can see that the reference of the constructed object cannot be visible to other threads before the constructor returns, because the final domain may not be initialized yet. After the constructor returns, any thread will ensure that the final Domain value is correctly initialized.
Implementation of final semantics in Processors
Now we take the x86 processor as an example to illustrate the specific implementation of final semantics in the processor.
As mentioned above, the re-sorting rules for writing final domains require the interpreter to insert a StoreStore Fault screen after writing the final domain and before the constructor returns. The re-sorting rules for read final fields require the compiler to insert a LoadLoad barrier before reading operations in the final domain.
Because the x86 processor does not re-Sort write operations, the StoreStore failure screen required to write the final domain will be omitted in the x86 processor. Similarly, because the x86 processor does not re-Sort operations with indirect dependencies, The LoadLoad barrier required to read the final domain is also omitted in the x86 processor. That is to say, in the x86 processor, the read/write of the final domain will not insert any memory barrier!
Why does JSR-133 enhance final semantics?
In the old Java memory model, the most serious defect is that the thread may see that the final Domain value will change. For example, if a thread currently sees the final field value of an integer as 0 (the default value before initialization), and then the thread reads the value of this final field after a while, it is found that the value is changed to 1 (the value after initialization by a thread ). The most common example is that in the old Java memory model, the value of String may change (see article 2 for a specific example. Interested readers can refer to it on their own, I won't go into details here ).
To fix this vulnerability, the JSR-133 Expert Group enhanced the final semantics. By adding write and read re-sorting rules for the final domain, java programmers can provide initialization security assurance: as long as the object is correctly constructed (the reference of the constructed object is not "escaped" in the constructor), no synchronization is required (the use of lock and volatile ), this ensures that any thread can see the final Domain value after initialization in the constructor.