While reading Java Concurrency in Practice book by Brian Goetz, I came across Data races and Race conditions.
Data races
A program is said to have a data race, and therefore not be a "properly synchronized" program, when there is a variable that is read by more than one thread, written by at least one thread, and the write and the reads are not ordered by a happens-before relationship.
Race condition
A race condition occurs when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the runtime; in other words, when getting the correct answer relies on lucky timing. The most common type of race condition is check-then-act, where a potentially stale observation is used to make a decision on what to do next
As I understand, Data race can be avoided by making sure that one or more of the above conditions hold false - ie, by making shared variables immutable or by making the access to them properly synchronized
.
My question is about the example of a SingletonFactory that is usually given to illustrate race condition.
e.g.:
public class SingletonFactory {
private Singleton singleton = null;
private SingletonFactory() {}
public Singleton getInstance() {
if(this.singleton == null) {
this.singleton = new Singleton();
}
return this.singleton;
}
}
Can this code can also be considered to cause a Data race?
I understand that one way to make the above program "completely thread safe" would be to have a double checked locking and also make the class variable volatile
.
But in case I just declare the Singleton
variable volatile
, but fail to synchronize the code block that initializes the variable, then can it be considered as safe at-least w.r.t "Data race", but still unsafe w.r.t. race condition? In general I am still in search of a good realistic example where there is no data race, but there is still a potential race condition!
(a blog that is usually referred to explain the difference between data race and race condition does not help me to understand this)
The Singleton pattern typically uses a single instance of a class that encloses a private static class field. The instance can be created using lazy initialization, which means that the instance is not created when the class loads but when it is first used.
As I understand, Data race can be avoided by making sure that one or more of the above conditions hold false - ie, by making shared variables immutable or by making the access to them properly synchronized .
A race condition occurs when the timing or order of events affects the correctness of a piece of code. Data Race. A data race occurs when one thread accesses a mutable object while another thread is writing to it.
Race condition occurs when multiple threads tries to modify singleton internal state. To make it thread safe, we can create serial queue, so at a time only one thread can execute.
There are three general flavors that are commonly seen with a lazy Singleton. The first is one without synchronization like your first example. The second, as you mentioned is double checked locking without volatile, and finally DCL with volatile. One contains a race-condition (if shared field is synchronized) and one contains a data-race.
public static class Singleton{
private static volatile Singleton INSTANCE; // volatile to illustrate the race condition
public static Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
In this case there doesn't happen to be a data-race but there is a race condition. The race here is that two or more threads can create a Singleton instance.
Now for a Double Checked locking example:
public static class Singleton{
private static Singleton INSTANCE; // not volatile to illustrate the data-race
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
In this case there is a data-race but not race-condition. The writes that occur to the INSTANCE variable may not be visible to other threads despite INSTANCE not being null.
So to answer your question.
My question is whether this code can also be considered to cause a Data race?
In your example, it contains both a data-race and a race-condition since neither the shared mutable variable is synchronized nor the atomic action of check-then-set is synchronized.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With