In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-01 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article introduces the relevant knowledge of "final keyword parsing in JMM". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Reading and writing to the find field is more like normal variable access than the locks and volatile described earlier. For the find domain, the compiler and the processor follow two reordering rules:
Writing to a final field within the constructor and then assigning a reference to the constructed object to a reference variable cannot be reordered.
There is no reordering between the first reading of a reference to an object that contains a finalfield and the subsequent first reading of the finalfield.
Below, we illustrate these two rules with some example code:
Public class FinalExample {int I; / / ordinary variable final int j; / / final variable static FinalExample obj; public void FinalExample () {/ / constructor I = 1; / / write ordinary field j = 2 / / write final domain} public static void writer () {/ / write thread An 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 domain}}
Suppose that one thread An executes the writer () method, and then another thread B executes the reader () method. Let's illustrate these two rules through the interaction between the two threads.
Write reordering rules for final fields
The reordering rule for writing final fields forbids reordering writes for final fields outside the constructor. The implementation of this rule includes the following two aspects:
JMM forbids the compiler from reordering the writes of the find field outside the constructor.
The compiler inserts a StoreStore barrier after the finalfield is written and before the constructor return. This barrier prevents the processor from reordering the writes of the finalfield outside 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:
Construct an object of type FinalExample
Assign the reference to this object to the reference variable obj.
Assuming that there is no reordering between the thread B read object reference and the member domain of the read object (I will explain why this assumption is needed soon), the following figure is a possible execution timing:
In the figure above, the operation of writing to the normal field is reordered by the compiler outside the constructor, and the reading thread B mistakenly reads the value of the normal variable I before initialization. On the other hand, the operation of writing the final field is "limited" to the constructor by the reordering rules of the write final field, and the reading thread B correctly reads the initialized value of the finalfield variable.
Writing a reordering rule for the final domain ensures that the finalfield of the object is properly initialized before the object reference is visible to any thread, and the normal domain does not have this guarantee. As an example in the above figure, when the reader thread B "sees" the object referencing obj, it is likely that the obj object has not yet been constructed (the write to the normal field I is reordered outside the constructor, and the initial value 2 has not yet been written to the normal field I).
Read the reordering rules for the finalfield
The reordering rules for reading the finalfield are as follows:
In a thread, JMM forbids the processor to reorder both the reference to the first read object and the find field contained in the object for the first time (note that this rule applies only to processors). The compiler inserts a LoadLoad barrier before reading the find domain operation.
There is an indirect dependency between the reference of the first read object and the finalfield contained in the object for the first time. Because the compiler adheres to 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, there are a few processors that allow reordering of operations that have indirect dependencies (such as alpha processors), and this rule is specific to such processors.
The reader () method consists of three operations:
Read the reference variable obj for the first time
The reference variable obj points to the normal field j of the object for the first time.
The first reading of the reference variable obj points to the finalfield I of the object.
Now let's assume that no reordering occurs on writer thread An and that the program executes on processors that do not comply with indirect dependencies. Here is a possible execution timing:
In the figure above, the operation of reading the normal domain of the object is reordered by the processor before the read object reference. When reading a normal domain, the domain has not yet been written by write thread A, which is an incorrect read operation. The reordering rule of reading the final domain "restricts" the operation of the read object finalfield after the read object reference, when the finaldomain has been initialized by the A thread, which is a correct read operation.
Reading the reordering rules for the finalfield ensures that before reading an object's finalfield, you must read the references to the objects that contain the finalfield. In this sample program, if the reference is not null, then the finalfield of the referenced object must have been initialized by the A thread.
If the finalfield is a reference type
The finalfield we saw above is the basic data type. Let's see what will happen if the finalfield is a reference type.
Look at 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 An 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 find field is a reference type, which refers to an int array object. For reference types, the reordering rule for writing the find field adds the following constraints to the compiler and processor:
Writing to the member field of an object referenced by final within the constructor and then assigning the reference to the constructed object to a reference variable outside the constructor cannot be reordered.
For the above example program, we assume that thread An executes the writerOne () method first, thread B executes the writerTwo () method after execution, and thread C executes the reader () method after execution. Here is a possible timing of thread execution:
In the figure above, 1 is the writing of the finalfield, 2 is the writing of the member domain of the object referenced by the finaldomain, and 3 is assigning the reference of the constructed object to a reference variable. In addition to the previously mentioned 1 and 3 cannot be reordered, 2 and 3 cannot be reordered either.
JMM ensures that the reader thread C can at least see the write thread A writing to the member domain of the final reference object in the constructor. That is, C can see that the value of array subscript 0 is at least 1. While thread B writes array elements, thread C may or may not see it. JMM does not guarantee that writes from thread B are visible to thread C, because there is data competition between thread B and thread C, and the execution result is unpredictable.
If you want to ensure that reader thread C sees the write of array elements by writer thread B, a synchronization primitive (lock or volatile) needs to be used between writer thread B and reader thread C to ensure memory visibility.
Why can't final references "escape" from within the constructor
As we mentioned earlier, writing a reordering rule for the finalfield ensures that the finalfield of the object to which the reference variable points to is properly initialized in the constructor before the reference variable is visible to any thread. In fact, to achieve this effect, we also need a guarantee: inside the constructor, the reference to the constructed object cannot be made visible to other threads, that is, the object reference cannot "escape" in the constructor. To illustrate, let's look at the following sample code:
Public class FinalReferenceEscapeExample {final int iposition static FinalReferenceEscapeExample obj;public FinalReferenceEscapeExample () {I = 1; / 1 write final domain obj = this; / / 2 this reference here "escape"} public static void writer () {new FinalReferenceEscapeExample () } public static void reader {if (obj! = null) {/ / 3 int temp = obj.i; / / 4}
Suppose one thread An executes the writer () method and the other thread B executes the reader () method. Operation 2 here makes the object visible to thread B before the construction is completed. Even if operation 2 here is the last step of the constructor, and even if operation 2 comes after operation 1 in the program, the thread executing the read () method may not be able to see the initialized value of the finalfield, because there may be a reorder between operation 1 and operation 2. The actual execution timing may be shown in the following figure:
As we can see from the above figure, the reference to the constructed object cannot be visible to other threads until the constructor returns, because the finalfield may not have been initialized at this time. After the constructor returns, any thread will ensure that the value of the finalfield is properly initialized.
Implementation of final semantics in processor
Now let's take the x86 processor as an example to illustrate the specific implementation of final semantics in the processor.
As we mentioned above, the reordering rule for writing a StoreStore field requires the translator to insert a StoreStore screen after writing the finalfield and before the constructor return. The reordering rule for reading the finalfield requires the compiler to insert a LoadLoad barrier before the operation of the read finalfield.
Because the x86 processor does not reorder write-write operations, the StoreStore barrier required to write the finalfield is omitted in the x86 processor. Similarly, because x86 processors do not reorder operations that have indirect dependencies, the LoadLoad barrier needed to read the finite domain is omitted in x86 processors. That is to say, in x86 processors, the read / write of the finaldomain will not insert any memory barrier!
So much for the introduction of "final keyword parsing in JMM". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.