Multithreading

What is Multithreading

Spread the love

Multithreading is a programming technique that allows a single process to run multiple threads concurrently. Threads are smaller, lightweight units of a process that share the same memory and resources. By using multithreading, programs can perform multiple tasks at the same time, making them more efficient and responsive.

Threads within the same process share memory and resources, making them lightweight and efficient for performing tasks in parallel.

Multithreading is useful in below Scenario

Parallel Execution: Multithreading enables different parts of a program to run concurrently, improving performance and responsiveness.

Improving performance : By allowing threads to run concurrently, multithreading can make better use of cpu resources, leading to faster execution of tasks.

Efficient Resource Usage: By sharing the process’s memory and resources, threads are more efficient than creating multiple processes.

Scalability: Multithreading can help applications scale better on multicore processors leveraging the full potential of modern hardware .

Multithreading is particularly useful for taking advantage of multi-core processors, where each thread can run on a separate core.However, it requires careful management to avoid issues like:

Resource contention: When threads compete for limited system resources.

Race conditions: When two or more threads try to modify shared data simultaneously.

Deadlocks: Deadlocks occur when multiple threads become stuck, each waiting for the other to release resources, causing a system halt.

How Thread Will Allocate Memory

In multithreading, threads share the same memory space and resources of the process they belong to. Here’s how memory allocation works for threads:

  1. Shared Memory: All threads in a process share the same heap memory and global variables. This allows them to communicate and share data efficiently.
  2. Thread-Specific Memory: Each thread has its own stack memory, used for storing its local variables and function call information. This stack memory is separate for each thread to prevent interference between them.
  3. Resource Allocation: When a thread is created, the operating system allocates stack memory and essential resources for execution. The stack size is configurable and varies based on the system or programming language.
  4. Synchronization and Safety: Since threads share heap memory, accessing shared data requires proper synchronization (like mutexes, locks, or semaphores) to avoid race conditions or data corruption.
Thread life cycle

The thread lifecycle in Java describes the various states a thread can go through from its creation to termination.

Thread States
  1. New:
    • A thread is in the created state when it has been initialized but has not started execution.
    • Example: Thread t = new Thread();
    • The thread is in the “New” state and hasn’t executed its run() method yet.
  2. Runnable:
    • When the start() method is called, the thread enters the “Runnable” state.
    • The thread is ready to run but waits for CPU time to execute.
    • Example: t.start();
  3. Running:
    • The thread is actively executing its run() method. The JVM scheduler selects it for execution.
    • Note: A thread alternates between “Runnable” and “Running” depending on CPU availability.
  4. Blocked/Waiting/Timed Waiting:
    • A thread moves to these states when it waits for resources or another thread’s signal:
      • Blocked: Waiting for a resource (e.g., a lock) to become available.
      • Waiting: Waiting indefinitely for another thread to signal (using methods like wait()).
      • Timed Waiting: Waiting for a specific time (using methods like sleep() or join(timeout)).
  5. Terminated (Dead):
    • A thread enters the ‘Terminated’ state when it completes execution or stops due to an exception.
    • Example: When the run() method finishes, the thread is considered “dead.”
How many way we can create thread .

Two way we can create a thread in java using Thread class and Runnable Interface

  1. Creating Threads Using the Thread Class
// Extending the Thread class
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello from MyThread: " + Thread.currentThread().getName());
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        // Creating and starting threads
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        thread1.start(); // Start thread1
        thread2.start(); // Start thread2
    }
}
Key Points:
  1. start() Method: This is used to begin thread execution, invoking the run() method internally.
  2. run() Method: Contains the code to be executed by the thread.
  3. Thread Identification: Thread.currentThread().getName() helps identify the currently executing thread.
Memory Allocation of Above Program
  1. Heap Memory:
    • When MyThread thread1 = new MyThread(); and MyThread thread2 = new MyThread(); are executed, two objects of the MyThread class are created in the heap memory.
    • The heap is a shared memory space accessible by all threads. Here, the MyThread objects (instances thread1 and thread2) reside.
  2. Stack Memory:
    • Each thread (thread1 and thread2) has its own stack memory.
    • The stack is private to each thread and used for storing local variables and method call information.
    • When the run() method executes for each thread, the method’s context (parameters, local variables, and return address) is stored in its respective stack.
  3. Shared Resources:
    • The code inside the run() method does not use shared variables; it only prints the thread’s name using Thread.currentThread().getName().
  4. Thread-Specific Memory:
    • The Thread.currentThread() object represents the current thread and exists in thread-specific storage. Each thread maintains its own identity and state (such as name, priority, etc.) in this storage.
  5. Garbage Collection:
    • Once thread1 and thread2 complete execution, they are eligible for garbage collection since there are no active references to these objects. The JVM automatically handles this cleanup process.

2.Creating Threads Using the Runnable Interface

class MyRunnable implements Runnable {   
 @Override
    public void run() {
        System.out.println("Hello from MyRunnable: " + Thread.currentThread().getName());
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        // Creating and starting threads
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());

        thread1.start(); // Start thread1
        thread2.start(); // Start thread2
    }
}

Above code explanation

1. class MyRunnable implements Runnable {

  • A new class MyRunnable is defined, which implements the Runnable interface.
  • This means that MyRunnable must provide an implementation for the run() method, as required by the Runnable interface.

2. @Override public void run() {

  • The run() method is overridden in the MyRunnable class.
  • When a thread executes, the code inside this run() method is what gets executed.

3. System.out.println("Hello from MyRunnable: " + Thread.currentThread().getName());

  • The run() method prints a message to the console with the name of the current thread (Thread.currentThread().getName()).
  • Thread.currentThread() retrieves the thread currently executing the run() method.

4. public class RunnableExample {

  • The RunnableExample class acts as the entry point for the program. Its main() method is where the threads are created and started.

5. Thread thread1 = new Thread(new MyRunnable());

  • A new Thread object is created, and the MyRunnable instance is passed as its argument.
    • Memory Allocation:
      • An object of MyRunnable is created in heap memory.
      • The Thread object thread1 is also created in heap memory.

6. Thread thread2 = new Thread(new MyRunnable());

  • Similarly, another Thread object is created, with another MyRunnable instance as its argument.
    • Memory Allocation:
      • A second MyRunnable object is created in heap memory.
      • The Thread object thread2 is also stored in heap memory.

7. thread1.start();

  • The start() method is called on thread1.
    • This triggers the JVM to execute the run() method of the MyRunnable instance associated with thread1.
    • Memory Allocation:
      • A new stack memory is allocated for thread1. The stack contains local variables and execution context for the run() method.

8. thread2.start();

  • Similarly, the start() method is called on thread2.
    • This triggers the JVM to execute the run() method of the second MyRunnable instance in a new thread.
    • Memory Allocation:
      • A new stack memory is allocated for thread2.
Memory Management Overview
  1. Heap Memory:
    • The MyRunnable instances (new MyRunnable()) and Thread objects (new Thread()) are allocated here.
    • .
  2. Stack Memory:
    • Each thread (thread1 and thread2) gets its own stack memory.
    • The stack holds:
      • Local variables.
      • Method call information for the run() method.
  3. Thread-Specific Information:
    • Each thread maintains its own state (like priority, name, etc.) in thread-specific memory managed by the JVM.
  4. Garbage Collection:
    • Once thread1 and thread2 finish their execution, the MyRunnable objects and Thread objects are eligible for garbage collection unless referenced elsewhere.

Leave a Reply

Your email address will not be published. Required fields are marked *