From the school to A factory all the way sunshine vicissitudes of life
Please go to www.codercc.com
1. Introduction to the final
Final can modify variables, methods, and classes to indicate that the modified content will not be changed once the value is assigned. For example, the String class is a class of final type. Even if we know how to use final in detail, I think it is easy to ignore the reordering problem of final in multi-threading. I hope we can discuss it together.
2. Specific application scenarios of final
Final can modify variables, methods, and classes, which means that the use of final covers almost every part of Java. Here are the locations where locks modify variables, methods, and classes.
2.1 variable
Variables in Java can be divided into member variables and method local variables. So follow this sequence to avoid missing any dead spots.
2.1.1 Final Member variables
In general, member variables in each class can be divided into class variables (static modified variables) and instance variables. The timing of assigning initial values to these two types of variables is different. Class variables can be assigned their initial values directly when they are declared or in static code blocks. Instance variables, on the other hand, can be assigned initial values at variable declaration time, in non-static initializer blocks, and in constructors. Class variables have two times to assign initial values, whereas instance variables have three times to assign initial values. Implicit initialization is not performed when final variables are not initialized, and an error occurs. This is still a bit abstract, but let’s use concrete code to demonstrate. (the code covers all possible cases of final modifier variables, so it pays to be patient 🙂
Each situation has been sorted out by looking at the picture above. I use a screenshot here to make the red error mark in the IDE more clear. Now let’s summarize these cases:
- Class variables: An initial value must be specified in a static initialization block or when a class variable is declared, and only in one of these two places;
- Instance variable: It is necessary to declare the instance variable in a non-static initializer block, or to specify an initial value in a constructor, and only in these three places.
2.2.2 Final Local variables
Final local variables are explicitly initialized by the programmer. If a final local variable has already been initialized, it cannot be changed again later. If a final variable has not been initialized, it can be assigned. Final local variables are shown in the following code:
Now let’s think about it another way. Final modifies basic data types and reference types. Is there a difference?
Final Base data type VS Final reference data type
As we have seen from the above example, if final modifies data of a primitive data type and cannot be changed once assigned, what if final is a reference data type? Can the referenced object be changed? Let’s also look at a piece of code.
Public class FinalExample {private final static Person = new Person(24, 170); private final static Person = new Person(24, 170); Public static void main(String[] args) {person.age = 22; System.out.println(person.toString()); } static class Person { private int age; private int height; public Person(int age, int height) { this.age = age; this.height = height; } @Override public String toString() { return "Person{" + "age=" + age + ", height=" + height + '}'; }}}Copy the code
When we change the property of person, the reference datatype variable to the final modifier, to 22, we can operate successfully. From this experiment, we can see that when final modifies a primitive variable, the primitive variable cannot be reassigned, so the primitive variable cannot be changed. For reference type variables, it only stores a reference, and final only guarantees that the address referenced by the reference type variable will not change, that is, the object will always be referenced, but the object property can be changed.
Macro variables
Using the immutable nature of a final variable, the variable becomes a “macro variable,” that is, a constant, if the following three conditions are met.
- Modify with the final modifier;
- The initial value is specified when the final variable is defined;
- This initial value can be uniquely specified at compile time.
Note: Where the macro variable is used elsewhere in the program, the compiler replaces it directly with the value of the variable
2.2 methods
Rewrite?
When a method of a parent class is final, a subclass cannot override it. For example, in Object, getClass() is final, so we cannot override it, but hashCode() is not final. We can override the hashCode() method. Let’s write an example to clarify this: define a parent class with the final modifier test();
public class FinalExampleParent {
public final void test() {
}
}
Copy the code
FinalExample then inherits the parent class and fails to override the test() method.
From this we can see that methods modified by final cannot be overridden by subclasses.
Overloaded?
public class FinalExampleParent {
public final void test() {
}
public final void test(String str) {
}
}
Copy the code
You can see that methods decorated with final can be overloaded. The following conclusions can be drawn from our analysis:
1. Final methods of a parent class cannot be overridden by subclasses
2. Final methods can be overloaded
2.3 class
When a class is modified by final, the table name class cannot be inherited by subclasses. Subclass inheritance often overrides the methods of the parent class and changes the attributes of the parent class, which is a security risk. Therefore, final decoration can be used when a class does not want to be inherited. Here’s a quick example:
public final class FinalExampleParent {
public final void test() {
}
}
Copy the code
The parent class is final, and when a subclass inherits the parent class, an error is reported, as shown in the following figure:
3. Final examples
Final is often used on immutable classes to take advantage of the immodification of final. Let’s first look at what an immutable class is.
The same class
An immutable class means that after an instance of the class is created, the instance variables of that instance are immutable. A class can be immutable if it meets the following conditions:
- Use the private and final modifiers to modify the member variables of the class
- Provide parameterized constructors for initializing class member variables;
- Provide getters only for member variables of the class, not setters, because regular methods cannot modify finA-modified member variables.
- Override the hashCode() and equals() methods of the Object class if necessary. Ensure that equals() is used to determine that the hashCode values of two identical objects are equal.
The eight wrapper classes provided in the JDK and the String class are immutable classes. Let’s take a look at the implementation of String.
/** The value is used for character storage. */
private final char value[];
Copy the code
The value of a String is final.
4. Do you really know final in multi-threading?
The use of final we talked about above should belong to the basic level of Java. After understanding these, can we really master final? Have you ever considered final in multi-threaded concurrency? In the Java memory model we know that the Java memory model has very few constraints on the bottom layer in order for the processor and compiler bottom layer to get the most out of them, which means that the Java memory model is a weak memory data model for the bottom layer. At the same time, processors and compilers have compiler and handler reordering of instruction sequences for performance optimization. So, ina multi-threaded case, how will final be reordered? Does it cause thread safety issues? Now, let’s look at reordering final.
4.1 Reordering Rules for final Domains
4.1.1 Final Fields are basic types
Let’s take a look at some sample code:
public class FinalDemo { private int a; Private final int b; // Final domain private static FinalDemo FinalDemo; public FinalDemo() { a = 1; // 1. Write common field b = 2; } public static void writer() {finalDemo = new finalDemo (); } public static void reader() { FinalDemo demo = finalDemo; Int a = demo.a; Int b = demo.b; //5. Read final fields}}Copy the code
Suppose thread A is executing writer() and thread B is executing reader().
Write the final field reorder
The reorder rule for writing final fields forbids reordering of writes to final fields outside the constructor. The implementation of this rule has two main aspects:
- The JMM forbids the compiler from reordering writes to final fields outside the constructor;
- The compiler inserts a StoreStore barrier (see this article on memory barriers) after the final field is written, but before the constructor return. This barrier prevents the processor from reordering writes to final fields outside the constructor.
The Writer method does two things, although it’s only one line of code:
- Construct a FinalDemo object;
- Assign this object to the member variable finalDemo.
Let’s draw a possible execution sequence diagram as follows:
Since there are no data dependencies between a and B, the common field (the common variable) A might be reordered outside the constructor, and thread B might read the value of the common variable before it was initialized (zero), which could lead to an error. The final field variable B, according to the reordering rules, prevents the final modified variable B from being reordered outside the constructor, so that B can be properly assigned and thread B can read the initialized value of the final variable.
Therefore, reordering rules that write final fields ensure that an object’s final field is properly initialized before its reference is visible to any thread, a guarantee that normal fields do not have. For example, in the above example, thread B could be an improperly initialized finalDemo object.
Read the final field reordering rules
The JMM disallows the reordering of the first read of an object reference and the first read of the final field that the object contains within a thread. (Note that this rule applies only to the processor.) The processor inserts a LoadLoad barrier before reading a final field operation. In fact, there is an indirect dependency between the reference to the read object and the final field that reads the object, and typically the processor does not reorder these two operations. However, some processors reorder, so this disallow reorder rule is for those processors.
The read() method consists of three operations:
- First read the reference variable finalDemo;
- Read the common field A of the reference variable finalDemo for the first time;
- First read the reference variable finalDemo final and B;
Assuming that thread A’s write process is not reordered, thread A and thread B have A possible execution sequence as follows:
The normal field of the read object is reordered before the reference of the read object. Thread B is reading the normal field variable of the read object before the reference of the read object. This is obviously the wrong operation. A final field read avoids this by “qualifying” that a reference to the object has been read before a final field variable is read.
Reorder rules for reading final fields ensure that the reference to the object containing the final field of an object is read before reading the final field of the object.
4.1.2 Final fields are reference types
Now that we know what reordering rules look like when final fields are basic data types, right? What if it’s a reference data type? Let’s move on.
Write to the member field of a final modified object
According to reference data types, writing for the compiler and final domain in highly increased such constraints: in the constructor of a final modified objects to members of the domain, and then in the constructor to this is constructed object reference is assigned to a reference variable, the two operations cannot be reorder. Note that the word “increment” means that the previous reorder for the final base data type is still used here. This sentence is a mouthful, the following combined with examples.
public class FinalReferenceDemo {
final int[] arrays;
private FinalReferenceDemo finalReferenceDemo;
public FinalReferenceDemo() {
arrays = new int[1]; //1
arrays[0] = 1; //2
}
public void writerOne() {
finalReferenceDemo = new FinalReferenceDemo(); //3
}
public void writerTwo() {
arrays[0] = 2; //4
}
public void reader() {
if (finalReferenceDemo != null) { //5
int temp = finalReferenceDemo.arrays[0]; //6
}
}
}
Copy the code
For the example above, thread A executes the wirterOne method, thread B executes the writerTwo method, and thread C executes the Reader method. The diagram below illustrates a situation that occurs with this execution sequence (patience is rewarded).
Since writes to final fields prohibit reordering out of the constructor, 1 and 3 cannot be reordered. Since a member field write to a reference object ina final field cannot be reordered with the subsequent assignment of the constructed object to a reference variable, 2 and 3 cannot be reordered.
Member field read operations on objects that are final modified
The JMM ensures that thread C will at least see writerThread A’s writes to the member fields of final referenced objects (i.e., arrays[0] = 1), whereas writer thread B’s writes to array elements may or may not be seen. The JMM does not guarantee that writes from thread B are visible to thread C, and there is a data race between thread B and thread C, where the outcome is unpredictable. If visible, use locking or volatile.
Summary of final reordering
By final modified data type:
Basic data types:
- Final field writes: Disallows final field writes and constructor reordering, that is, disallows final field writes to be reordered outside of the constructor to ensure that the object’s final fields are fully initialized before the object is visible to all threads.
- Final field reads: Disallows references to the first read object and reordering of the final fields that the read object contains.
Reference data types:
Additional constraint: Disallows writes to the member field of a final-modified object in the constructor and subsequent reordering of references to the constructed object
5. Realization principle of final
As mentioned above, writing a final field requires the compiler to insert a StoreStore barrier after the final field is written and before the constructor returns. Reordering rules for reading final fields require the compiler to insert a LoadLoad barrier before reading operations in final fields.
Interestingly, if X86 processing is taken as an example, X86 does not reorder write-write, so the StoreStore barrier can be omitted. Since operations that have indirect dependencies are not reordered, the LoadLoad barrier required to read final fields is also omitted on X86 processors. In other words, for X86, the memory barrier for reading/writing final domains is omitted! Does it plug in or does it depend on what processor it is
6. Why final references cannot “overflow” from constructors
Here’s another interesting issue: writing reordering rules for final fields ensures that when we use an object reference, the object’s final field has already been initialized in the constructor. There is, however, a prerequisite: the object being constructed cannot be visible to other threads in the constructor, meaning that the object reference cannot “escape” from the constructor. Take the following example:
public class FinalReferenceEscapeDemo {
private final int a;
private FinalReferenceEscapeDemo referenceDemo;
public FinalReferenceEscapeDemo() {
a = 1; //1
referenceDemo = this; //2
}
public void writer() {
new FinalReferenceEscapeDemo();
}
public void reader() {
if (referenceDemo != null) { //3
int temp = referenceDemo.a; //4
}
}
}
Copy the code
The possible execution sequence is shown below:
Suppose one thread, A, executes writer and another thread executes reader. Since there is no data dependency between operations 1 and 2 in the constructor, 1 and 2 can be reordered, executing 2 first, at which point the reference object referenceDemo is an incomplete initialized object that thread B fails to read. The final field write reordering rule is still met, though: by the time a reference object is visible to all threads, its final field has been fully initialized. However, if the reference object “this” escapes, the code still has thread-safety issues.
Refer to the literature
The Art of Concurrent Programming in Java
Crazy Java Handouts