Java safe coding guide: thread safety rules
brief introduction
If we introduce shared variables into multithreading, we need to consider thread safety in multithreading. So what thread safety issues should we pay attention to in the process of writing code?
Let's have a look.
Note the rewriting of thread safe methods
Everyone has done method rewriting. We know that method rewriting does not check method modifiers, that is, we can rewrite a synchronized method into a non thread safe method:
public class SafeA {
public synchronized void doSomething(){
}
}
public class UnsafeB extends SafeA{
@Override
public void doSomething(){
}
}
When implementing subclass functions, we must maintain the thread safety of methods.
Overflow of this in constructor
What is this? According to the JLS specification, when used as the main expression, the keyword this represents a value that is a reference to the object that calls the instance method or the object being constructed.
Then the problem comes, because this can represent the object being constructed, which means that if the object has not been constructed and this can be accessed externally, it will cause the problem that the external object accesses the object that has not been constructed successfully.
Let's take a look at what happens when this overflow occurs:
public class ChildUnsafe1 {
public static ChildUnsafe1 childUnsafe1;
int age;
ChildUnsafe1(int age){
childUnsafe1 = this;
this.age = age;
}
}
The above is a very simple case of this overflow. Assigning this to a public object in the constructor process will cause this to be accessed by other objects before it has been initialized.
Can we just adjust the order?
public class ChildUnsafe2 {
public static ChildUnsafe2 childUnsafe2;
int age;
ChildUnsafe2(int age){
this.age = age;
childUnsafe2 = this;
}
}
As we can see above, the assignment of this is placed at the end of the constructor. Can we avoid accessing uninitialized objects?
The answer is no, because Java will reorder the code, so the position of childunsafe2 = this is uncertain.
We need to modify it as follows:
public class Childsafe2 {
public volatile static Childsafe2 childUnsafe2;
int age;
Childsafe2(int age){
this.age = age;
childUnsafe2 = this;
}
}
Add a volatile descriptor to prohibit reordering and solve it perfectly.
Let's take another look at the parent-child class, or childsafe2 above. Let's write a subclass for it:
public class ChildUnsafe3 extends Childsafe2{
private Object obj;
ChildUnsafe3(int age){
super(10);
obj= new Object();
}
public void doSomething(){
System.out.println(obj.toString());
}
}
What's wrong with the above example? Because the parent class has exposed this variable when calling the constructor, it may cause the external program to call dosomething () before obj in childunsafe3 has been initialized. At this time, obj has not been initialized, so NullPointerException will be thrown.
The solution is not to set this in the constructor. We can create a new method and set it after the constructor is called.
Do not use background threads during class initialization
If a background process is used during class initialization, it may cause deadlock. We consider the following situations:
public final class ChildFactory {
private static int age;
static {
Thread ageInitializerThread = new Thread(()->{
System.out.println("in thread running");
age=10;
});
ageInitializerThread.start();
try {
ageInitializerThread.join();
} catch (InterruptedException ie) {
throw new AssertionError(ie);
}
}
public static int getAge() {
if (age == 0) {
throw new IllegalStateException("Error initializing age");
}
return age;
}
public static void main(String[] args) {
int age = getAge();
}
}
The above class uses a static block in which we start a background process to set the age field.
In order to ensure visibility, the static variable must be initialized before other threads run, so ageinitializerthread can run only after the static variable of the main thread is executed, but we called ageinitializerthread In the join () method, the main thread needs to wait for the execution of ageinitializerthread in turn.
Finally, it leads to circular waiting and deadlock.
The simplest solution is to directly set in the static block without using the background process:
public final class ChildFactory2 {
private static int age;
static {
System.out.println("in thread running");
age=10;
}
public static int getAge() {
if (age == 0) {
throw new IllegalStateException("Error initializing age");
}
return age;
}
public static void main(String[] args) {
int age = getAge();
}
}
Another way is to use ThreadLocal to save the initialization variables locally.
public final class ChildFactory3 {
private static final ThreadLocal<Integer> ageHolder = ThreadLocal.withInitial(() -> 10);
public static int getAge() {
int localAge = ageHolder.get();
if (localAge == 0) {
throw new IllegalStateException("Error initializing age");
}
return localAge;
}
public static void main(String[] args) {
int age = getAge();
}
}
Code for this article:
learn-java-base-9-to-20/tree/master/security