Corporate Training
Request Demo
Click me
Menu
Let's Talk
Request Demo

Tutorials

Threads and Concurrency

17. Threads and Concurrency

Multithreading and concurrency are essential concepts in Java that allow you to execute multiple tasks concurrently, taking full advantage of modern multi-core processors. In this Core Java tutorial, we'll explore threads and concurrency in detail, providing explanations and examples.

What Are Threads?

A thread is a lightweight, independent path of execution within a Java program. Threads are used to perform multiple tasks simultaneously, allowing your program to make better use of CPU resources and improve responsiveness.

Creating Threads:

There are two primary ways to create threads in Java:

  • Extending the 'Thread' Class: You can create a new class that extends the 'Thread' class and override the 'run' method to define the code to be executed by the thread.
class MyThread extends Thread {
    public void run() {
        // Code to be executed by the thread
    }
}

// Creating and starting a new thread
MyThread thread = new MyThread();
thread.start();
 
  • Implementing the 'Runnable' Interface: You can create a class that implements the 'Runnable' interface and override the 'run' method. Then, you can create a 'Thread' object, passing an instance of your class to its constructor.
class MyRunnable implements Runnable {
    public void run() {
        // Code to be executed by the thread
    }
}

// Creating a Runnable instance
MyRunnable myRunnable = new MyRunnable();

// Creating and starting a new thread
Thread thread = new Thread(myRunnable);
thread.start();
 

Thread States:

Threads in Java can be in different states throughout their lifecycle. The key states include:

  • New: The thread is created but not yet started.
  • Runnable: The thread is ready to run and can be scheduled for execution.
  • Blocked/Waiting: The thread is temporarily inactive, waiting for some condition to be met.
  • Terminated: The thread has completed its execution and has terminated.

Synchronization:

In a multithreaded environment, it's common for multiple threads to access shared resources concurrently, potentially leading to data corruption or race conditions. To address this, Java provides synchronization mechanisms like the 'synchronized' keyword and the 'Lock' interface.

Example of Synchronization using 'synchronized':
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
 

In this example, the 'synchronized' keyword ensures that only one thread can execute the 'increment'  and 'getCount' methods at a time, preventing race conditions.

Thread Pools:

Creating and managing individual threads can be costly in terms of system resources. Thread pools offer a solution by maintaining a pool of worker threads that can be reused for executing tasks.

Example of Using a Thread Pool:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // Create a thread pool with a fixed number of threads
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // Submit tasks for execution
        executor.submit(() -> System.out.println("Task 1 executed"));
        executor.submit(() -> System.out.println("Task 2 executed"));
        executor.submit(() -> System.out.println("Task 3 executed"));

        // Shutdown the thread pool
        executor.shutdown();
    }
}
 

In this example, we create a thread pool with three threads and submit tasks for execution. The thread pool manages the thread lifecycle, reusing threads when possible.

Java Concurrency Utilities:

Java provides a comprehensive set of concurrency utilities in the 'java.util.concurrent'  package, including:

  • 'ExecutorService': A higher-level replacement for managing threads and thread pools.
  • 'Future': Represents the result of an asynchronous computation.
  • 'Locks' (e.g., 'ReentrantLock'): Provides advanced locking mechanisms.
  • 'Atomic' classes (e.g., 'AtomicInteger', 'AtomicReference' ): Provides atomic operations for thread-safe updates.
  • 'BlockingQueue': Provides thread-safe data structures for managing queues.
  • 'CountDownLatch', 'CyclicBarrier','Semaphore': Synchronization aids for managing threads.

These utilities simplify complex concurrency scenarios and are highly recommended for building robust multithreaded applications.

Conclusion:

Multithreading and concurrency are powerful concepts in Java that enable you to maximize the utilization of modern hardware and improve application responsiveness. Understanding how to create threads, manage thread states, synchronize access to shared resources, and use thread pools and Java's concurrency utilities is essential for developing efficient and reliable Java applications.