Skip to content

Introduction to Multithreading in Java

Multithreading in Java is a feature that allows the concurrent execution of two or more threads within a single process. A thread is a lightweight process and the smallest unit of execution in a Java program. Multithreading helps maximize CPU utilization by executing multiple threads simultaneously, which can lead to more efficient and faster applications, especially in applications that perform time-consuming tasks.


Key Concepts of Multithreading

  1. Thread:
    1. A thread is a single line of execution in a program. It represents an independent path of execution.
    1. Each thread has its own program counter, stack, and local variables, but shares memory space with other threads in the same process.
  2. Process vs. Thread:
    1. Process: A self-contained execution environment with its own memory space.
    1. Thread: A smaller unit of a process that shares the process’s memory space.
  3. Multithreading Benefits:
    1. Better Resource Utilization: Utilizes CPU resources more efficiently by executing tasks concurrently.
    1. Improved Performance: Tasks that can be parallelized can run faster.
    1. Responsive Applications: Enhances the user experience by keeping the application responsive while executing background tasks.
    1. Simplified Code: Certain problems can be solved more effectively with concurrent programming (e.g., real-time data processing).

Creating Threads in Java

There are two main ways to create a thread in Java:

  1. Extending the Thread Class:
    1. You can create a thread by extending the Thread class and overriding its run() method.

Example:

class MyThread extends Thread {

    public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println(“Thread running: ” + i);

        }

    }

}

public class ThreadExample {

    public static void main(String[] args) {

        MyThread thread = new MyThread();

        thread.start(); // Start the thread; calls the run method in a new thread of execution

    }

}

  • Implementing the Runnable Interface:
    • You can create a thread by implementing the Runnable interface and passing an instance of the class to a Thread object.

Example:

class MyRunnable implements Runnable {

    public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println(“Runnable thread running: ” + i);

        }

    }

}

public class RunnableExample {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();

        Thread thread = new Thread(myRunnable);

        thread.start(); // Start the thread; calls the run method in a new thread of execution

    }

}


Thread Lifecycle

A thread in Java goes through several stages during its lifecycle:

  1. New: When a thread instance is created but not yet started.
  2. Runnable: When the start() method is called, the thread is ready to run and is waiting for CPU scheduling.
  3. Blocked: A thread enters this state when it is waiting for a resource that is currently unavailable (e.g., waiting for I/O or a monitor lock).
  4. Waiting: A thread is waiting for another thread to perform a particular action (e.g., wait() method).
  5. Timed Waiting: A thread is waiting for a specific period (e.g., sleep(milliseconds) or join(milliseconds)).
  6. Terminated: A thread is in this state when it has completed execution or has been terminated.

Thread Methods

  • start(): Starts a thread and calls the run() method.
  • run(): The code that defines what the thread will do when it is executed.
  • sleep(milliseconds): Causes the thread to pause for the specified number of milliseconds.
  • join(): Waits for the thread to complete before moving on.
  • yield(): Suggests to the scheduler to give up the current time slice and allow other threads to run.
  • interrupt(): Interrupts a thread that is in a blocked or waiting state.
  • isAlive(): Checks if the thread is still running.

Thread Synchronization

In multithreaded applications, synchronization is needed to prevent data inconsistency due to concurrent access to shared resources. Java provides several mechanisms for synchronization:

  1. Synchronized Methods:
    1. You can make a method synchronized by using the synchronized keyword, ensuring that only one thread can execute it at a time per object instance.

Example:

public synchronized void synchronizedMethod() {

    // Critical section code

}

  • Synchronized Blocks:
    • You can synchronize specific code blocks within a method to reduce the scope of synchronization.

Example:

public void someMethod() {

    synchronized (this) {

        // Critical section code

    }

}

  • Reentrant Locks:
    • The java.util.concurrent.locks package provides more flexible locking mechanisms, such as ReentrantLock.

Example:

Lock lock = new ReentrantLock();

lock.lock();

try {

    // Critical section code

} finally {

    lock.unlock();

}


Thread Priorities

Java threads have priorities that can affect the order of execution. Threads with higher priorities are more likely to be scheduled before those with lower priorities, but this is not guaranteed.

  • Priority Levels:
    • Thread.MIN_PRIORITY (1)
    • Thread.NORM_PRIORITY (5) – Default priority
    • Thread.MAX_PRIORITY (10)

Example:

Thread thread = new Thread();

thread.setPriority(Thread.MAX_PRIORITY);


Thread Safety

Thread safety is a property of an object or code that ensures correct behavior when multiple threads access it concurrently. To achieve thread safety:

  • Use synchronization techniques.
  • Use concurrent collections from the java.util.concurrent package (e.g., ConcurrentHashMap).
  • Avoid mutable shared data.
  • Use ThreadLocal for variables that are isolated to the current thread.

Conclusion

Multithreading is a powerful feature in Java that enhances the performance and responsiveness of applications by allowing concurrent execution of tasks. Understanding the basic concepts, thread lifecycle, synchronization, and thread safety are crucial for developing effective multithreaded applications. With careful design and proper use of synchronization, Java developers can create robust, thread-safe programs that make the most of modern multi-core processors.