Concurrent Programming Fundamentals— Thread Safety
What is thread safety ?
thread-safety or thread-safe code in Java refers to code that can safely be utilized or shared in concurrent or multi-threading environment and they will behave as expected.
Thread-safety is one of the risks introduced by using threads in Java and I have seen java programmers and developers struggling to write thread-safe code or just understanding what is thread-safe code and what is not?
How to make Thread-Safe Code in Java
Before you learn how to write a thread-safe code you need to understand what is thread-safety and there is no better way to that than looking at a non-thread-safe code. So, let’s see an example of the potential, not thread-safe code, and learn how to fix that.
Example of Non-Thread-Safe Code in Java
Here is an example of a non-thread-safe code, look at the code, and find out why this code is not thread-safe?
Example 1
public class NumberCounter {
private int count;
AtomicInteger atomicCount = new AtomicInteger( 0 ); public synchronized int getCount(){
return count++;
} public int getCountAtomically(){
return atomicCount.incrementAndGet();
}
}
The above example is not thread-safe because ++ (the increment operator) is not an atomic operation and can be broken down into reading, update, and write operations.
If multiple thread call getCount() approximately same time each of these three operations may coincide or overlap with each other for example while thread 1 is updating value, thread 2 reads and still gets old value, which eventually let thread 2 override thread 1 increment and one count is lost because multiple threads called it concurrently.
Example 2
public class BasicObservableClass { public interface Observer {
void onObservableChanged();
} private Set<Observer> mObservers; public void registerObserver(Observer observer) { if (observer == null) {
return;
} if (mObservers == null) {
mObservers = new HashSet<>(1);
} mObservers.add(observer); } public void unregisterObserver(Observer observer) { if (mObservers != null && observer != null) {
mObservers.remove(observer);
}
} private void notifyObservers() {
if (mObservers == null) {
return;
} for (Observer observer : mObservers) {
observer.onObservableChanged();
}
}
}
What can happen if we use non thread-safe implementation in multi-threaded environment:
In the most general case of multi-threaded environment we shall assume that all methods will be called on random threads at random times. The implementation we used for a single-threaded environment is not safe anymore. It is not hard to imagine a flow when two distinct threads attempt to register new Observers at the same time, and end up leaving our system screwed up. One such flow could be (there are many other flows which could break non-thread-safe implementation):
- Thread A invokes
registerObserver(Observer)
- Thread A executes
mObservers == null
check and proceeds to instantiation of a new set - before Thread A got a chance to create a new set, OS suspended it and resumed execution of Thread B
- Thread B executes steps 1 and 2 above
- since Thread A hasn’t instantiated the set yet, Thread B instantiates a new set and stores a reference to it in
mObservers
- Thread B adds an observer to the newly created set
- at some point OS resumes execution of Thread A (which was suspended right before instantiation of a new set)
- Thread A instantiates a new set and overrides the reference in
mObservers
- Thread A adds an observer to the newly created set
Despite the fact that both calls to registerObserver(Observer)
completed successfully, the end result is that only one observer will be notified when notifyObservers()
called. It is important to understand that any of the observers could end up being “ignored”, or both observers could be registered successfully – the outcome depends on the scheduling of threads by OS which we can’t control. This non-determinism is what makes multi-threading bugs very hard to track and resolve.
How to make Thread-Safe code in Java
There are multiple ways to make this code thread-safe in Java:
1) Use the synchronized keyword in Java and lock the getCount() method so that only one thread can execute it at a time which removes the possibility of coinciding or interleaving.
2) use Atomic Integer, which makes this ++ operation atomic and since atomic operations are thread-safe and saves the cost of external synchronization.
Here is a thread-safe version of NumberCounter class in Java:
public class NumberCounter { private int count;
AtomicInteger atomicCount = new AtomicInteger( 0 ); public synchronized int getCount(){
return count++;
} public int getCountAtomically(){
return atomicCount.incrementAndGet();
}}
Important points about Thread-Safety in Java
Here are some points worth remembering to write thread-safe code in Java, this knowledge also helps you to avoid some serious concurrency issues in Java-like race condition or deadlock in Java:
1) Immutable objects are by default thread-safe because their state can not be modified once created. Since String is immutable in Java, it’s inherently thread-safe.
2) Read-only or final variables in Java are also thread-safe in Java.
3) Locking is one way of achieving thread-safety in Java.
4) Static variables if not synchronized properly become a major cause of thread-safety issues.
5) Example of thread-safe class in Java: Vector, Hashtable, ConcurrentHashMap, String, etc.
6) Atomic operations in Java are thread-safe like reading a 32-bit int from memory because it’s an atomic operation it can’t interleave with other threads.
7) local variables are also thread-safe because each thread has there own copy and using local variables is a good way to write thread-safe code in Java.
8) In order to avoid thread-safety issues minimize the sharing of objects between multiple threads.
9) Volatile keyword in Java can also be used to instruct thread not to cache variables and read from main memory and can also instruct JVM not to reorder or optimize code from threading perspective.