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

Example of Non-Thread-Safe Code in Java

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:

  1. Thread A invokes registerObserver(Observer)
  2. Thread A executes mObservers == null check and proceeds to instantiation of a new set
  3. before Thread A got a chance to create a new set, OS suspended it and resumed execution of Thread B
  4. Thread B executes steps 1 and 2 above
  5. since Thread A hasn’t instantiated the set yet, Thread B instantiates a new set and stores a reference to it in mObservers
  6. Thread B adds an observer to the newly created set
  7. at some point OS resumes execution of Thread A (which was suspended right before instantiation of a new set)
  8. Thread A instantiates a new set and overrides the reference in mObservers
  9. 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

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

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.

Software Engineer @ hSenid Mobile | Former Software Engineering Intern @ WSO2 | Software Engineering Undergraduate @ IIT