Skip to content

Understanding ‐ Concurrency, Threading, Synchronization

Devrath edited this page Feb 11, 2024 · 14 revisions

banner-3-1920x600

Defining threads

  • A thread is defined at the operating system level
  • It is defined as a set of instructions
  • A process consists of a pool of threads
  • Different threads can be executed at the same time
  • Ex: JVM itself works with several threads, Such as Garbage-Collector(GC) and Just-In-Time(JIT) compiler

Understanding the term - "At the same time"

Screenshot 2024-02-01 at 6 23 55β€―PM
  • Consider you are writing the document and at the same time Spell check is run for the words that are written, and the print command might be invoked and you might also receive the email.

What is happening at the CPU level

If it is a single-core

Screenshot 2024-02-01 at 7 29 31β€―PM
  • You can observe that in a single core, actions are performed one after another synchronously, But since the actions take place so fast(ex: 5ms, 10ms), For the user it gives the feeling that all actions are happening at the same time.

If it is a double-core

Screenshot 2024-02-01 at 7 42 17β€―PM
  • In the multi-core, as you can observe multiple actions are happening at the same time distributed among two cores
  • So, On a multi-core CPU, actually things are happening at the same time :)

What is multi-threading

Single Code Multi Code
Screenshot 2024-02-03 at 3 04 25β€―PM Screenshot 2024-02-03 at 3 13 55β€―PM
  • Multi-threading means multiple multiple threads running in an application.
  • Thread is like a CPU running your application.
  • Thus multi-threaded applications are like separate parts of the CPU running your application simultaneously.
  • Why multi-threading is needed
    • Better utilization of a single CPU
    • Better utilization of multiple CPUs or CPU cores.
    • Better user experience about responsiveness.
    • Better user experience about fairness.

Concurrency vs Parallelism

Concurrent-Execution Parallel-Execution Parallel-Concurrent-Execution
Screenshot 2024-02-03 at 3 54 38β€―PM Screenshot 2024-02-03 at 3 58 46β€―PM Screenshot 2024-02-03 at 3 59 03β€―PM
  • Concurrency refers to how a single CPU can make progress on multiple tasks seemingly at the same time (AKA concurrently).
  • Parallelism, on the other hand, is related to how an application can parallelize the execution of a single task - typically by splitting the task up into subtasks that can be completed in parallel.

Who is responsible for sharing the CPU

  • CPU-SCHEDULER is responsible for dividing the timeline of the CPU tasks in the cores
  • Three reasons for the scheduler to pause the thread
    1. CPU should be shared equally among the threads.
    2. The thread is waiting for more data(Thread doing Ip/Op , Reading/Writing).
    3. The thread is waiting for another thread to perform some task.

What is a race condition

  • A race condition deals with Accessing data concurrently
  • Say 2 separate threads are trying to read and write the same variable, This will result in a race condition.

Analysis of race condition

Code that might cause the race condition

  • Consider 2 threads are accessing the Singleton instance
  • Say the first thread tries to access it, The control comes inside the if condition since the instance is null from the thread-1
  • Before it is initialized say another thread tries to access the same Singleton instance, Now say the variable is not initialized by the first thread yet, the second thread also allows the control to enter the if condition.
  • This would be the Ideal scenario of two threads accessing the same variable at this moment.
public class Singleton {
    // Private static instance variable
    private static Singleton instance;

    // Private constructor to prevent instantiation from outside the class
    private Singleton() { }

    // Public method to get the singleton instance
    public static Singleton getInstance() {
        // Lazy initialization - create the instance if it doesn't exist
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Making it synchronized

  • Here by introducing the synchronised keyword, It will give a special lock object that contains a key.
  • When a thread tries to access the synchronized block, it will take the key and enter the block of code and thus enter execution.
  • Now at the same time another thread tries to access the synchronized block, since there is no key it cannot access the block since another earlier thread has not finished execution and has not returned the key to the lock.
  • Now once the first thread finished the execution, it returns the key back and now the second thread can access it and can enter the execution
  • This would prevent the race-condition.
   public static synchronized Singleton getInstance() {
        // Lazy initialization - create the instance if it doesn't exist
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

Understanding the Lock Object in Synchronization

  • For the synchronization, We will need a special object that will hold onto the key.
  • The key is available in the Object class in Java, So making it available to all the classes in the code.

Using the synchronized keyword on the function for the lock object

Here the static method uses the class as the synchronization object

public static synchronized Singleton getInstance() {
      if (instance == null) {
            instance = new Singleton();
      }
      return instance;
}

Using the dedicated object for the synchronize

  • Here we use the dedicated object as the key to the synchronized block.
  • This is the best way to hold the synchronization whether we are in the static context or not.
private final Object key = new Object()
public string init(){
   synchronized(key){
       // Do some stuff
   }
}

Can a core run more than one thread

Hyperthreading (running multiple threads on a core at the β€œsame” time) doesn't truly run them at the same time, it effectively switches between them, but with pipelining, it may seem like it's running them at the same time.

What is a re-entrant lock

  • Say there is a Person-class, The person-class method is getName().
  • And there are 2 instances of person John and Sarah.
  • The getName() is synchronized
  • Now one of the threads is accessing getName() of the John.
  • So when the thread accesses it, It takes the key and will be using the method.
  • Now if there is a call from getName() of john to Sarah, This is possible since the thread that holds the key is the same thread accessing the other instance also.

What is a dead-lock

  • Deadlock is a situation, where T1 holds key needed by T2 and T2 holds key needed by T1