Java Multithreading - Atomic, Volatile, Threadlocal, Mutex and Semaphore

Java Multithreading - Atomic, Volatile, Threadlocal, Mutex and Semaphore

·

5 min read

This article covers the most important and distinctive aspects of different concepts in multithreading. Each of the section deserves its own deep dive and there are pages written in reference books on them. Aim is to superficially cover these concepts and provide github references wherever possible

Challenges in Multi-threading

  1. Visibility problem - if two threads work on two different cores, variable updated by Thread2 does not reflect to Thread1 because it is stored in local CPU cache

    addressed by likes of volatile variable

  2. Synchronization problem - variables which are shared between inter-dependent threads (counter variable) behave unpredictably or a critical resource (DB connection) gets exhausted due to concurrent access above capacity, these cases need synchronization to make them predictable or enforce controlled access

    addressed by likes of synchronized keyword or Atomic operations

Atomic variable

Why do we need atomic variables? Imagine a variable int count, getting incremented 5 times by 2 threads in parallel. It should have 10 as final value but it always varies between 5/6/7/8. We need atomic variables to make sure this doesn't happen.

How atomic variable can help? In above example, increment is 3 step machine level instructions operation (read, increment, set). Atomic variable uses compare-and-swap (CAS) algorithm which translates increment to 1 step machine level instruction. So when a tread increments the value, it's all or nothing operation.

A typical CAS operation works on three operands:

  • The memory location on which to operate (M)
  • The existing expected value (A) of the variable
  • The new value (B) which needs to be set
  • The CAS operation updates atomically the value in M to B, but only if the existing value in M matches A, otherwise no action is taken.

Why to use atomic when we have synchronized and volatile?

  • Synchronized has a performance hit to lock and release resources so its a bit slower and blocking in nature causing probably deadlocks or livelocks. Atomic operations are non blocking and don't have these problems.
  • Volatile variables addresses visibility problem but doesn't guarantee thread safety and synchronization

Volatile variable

How does volatile work? Volatile variable addresses visibility problem in multithreading world where read/write to a variable from ThreadA are not always visible to ThreadB. Marking a variable volatile means always read/write that variable from/to main memory and not from thread specific CPU cache, as illustrated below scan 2022-07-17 19.10.19n_9.jpg

Volatile is not always enough

Specially when variable needs to read the value first from main memory and generate new value based on that doesn't guarantee correct visibility creating race condition. Consider above example with race condition scan 2022-07-17 19.10.19n_10.jpg

This looks nice, should we make all variables volatile then?

This would not be a wise thing to do for various reasons, performance being primary. Accessing data from main memory is expensive as compared to CPU cache and will cause performance deterioration if all variables are in main memory.

Further reading

  • full volatile visibility guarantee
  • happens-before volatile guarantee

ThreadLocal variable

ThreadLocal is exactly opposite to volatile variable. Each thread gets its own copy of that variable and data is never shared across threads. So this rather enforces visibility of variables to each thread.

ThreadLocal<Integer> intVal = new ThreadLocal<>();

How does ThreadLocal works? To put it simply, ThreadLocal put data of the variable inside a map (ThreadLocalMap) with Thread as the key (weak reference). So value is retrieved from map based on which thread is calling it.

Important consideration while using ThreadLocal and ThreadPools

  • while using ThreadPool, thread returns to pool once job is done
  • when same thread is borrowed again from pool, it might have previous threadlocal data, if that is not explicitly cleaned up
  • we can avoid this by manually cleaning the threadlocal once done but that is error-prone and requires rigorous code reviews
  • we can also use ThreadPoolExecutor's beforeExecute() and afterExecute() methods to do the cleanups

ThreadLocal and Memory Leak

In web server and application servers like Tomcat or WebLogic, web-app is loaded by a different ClassLoader than which is used by Server itself. This ClassLoader loads and unloads classes from web applications. Web servers also maintain ThreadPool, which is a collection of the worker thread, to server HTTP requests. Now if one of the Servlets or any other Java class from web application creates a ThreadLocal variable during request processing and doesn't remove it after that, copy of that Object will remain with worker Thread and since life-span of worker Thread is more than web app itself, it will prevent the object and ClassLoader, which uploaded the web app, from being garbage collected. This will create a memory leak in Server.

Read more: javarevisited.blogspot.com/2013/01/threadlo..

Mutex

What is a Mutex? Limit access to a critical resource, in multithreading environment, to only one thread at a time. Mutex is the simplest form of synchronizer.

Example:

public class SequenceGenerator{
    private int count = 0;
    public int getSequence(){ return count++; }
}

How to implement Mutex? there are more ways to do this using third party libraries like Guava (monitor), covering basic ways here

  1. using synchronized method
    public synchronized int getSequence(){ return count++; }
    
  2. using synchronized block
    public Object mutex = new Object();
    public int getSequence(){ 
     public int getSequence(){
         synchronized(mutex){
             return count++;
         }
     }
    }
    
  3. using ReentrantLock
    private ReentrantLock mutex = new ReentrantLock();
    public int getSequence(){
     try{
         mutex.lock();
         return count++;
     }
     finally{
         mutex.unlock();
     }
    }
    
  4. using Semaphore as explained below, setting limit to 1

TODO: insert github references

Semaphore

used to limit number of concurrent connections to a resource

class SemaphoreTest{
  private Semaphore semaphore;
  public SemaphoreTest(int limit){
      semaphore = new Semaphore(limit);
  }
  // acquire permits or blocks until available
  public boolean getResourceBlocking(){
      return semaphore.acquire();
  }
  // try to acquire permit, returns false if not available instead of blocking
  public boolean getResourceNonBlocking(){
      return semaphore.tryAcquire();
  }
  // releases the lock/permit
  public void releaseResource(){
      semaphore.release();
  }
  // returns available slots
  public int availablePermits(){
      return semaphore.availablePermits();
  }
}

TODO: insert github reference

Apache commons has TimedSemaphore which releases all locks after specified timeout initialized as new TimedSemaphore(long period, TimeUnit.SECONDS, limit);

Did you find this article valuable?

Support Udaysinh by becoming a sponsor. Any amount is appreciated!