Multi-Threaded Programming in Java

Multi-Threaded Programming in Java (2025)

has robust built-in support for multi-threaded programming, allowing developers to execute multiple parts of a program concurrently. This is crucial for building responsive, scalable, and efficient applications that can leverage multi-core processors effectively in 2025.

Understanding Threads in Java

  • Threads: In Java, a thread is a lightweight sub-process, a separate flow of execution that can run concurrently with other threads within the same program. Threads share the same memory space of the process.
  • Concurrency vs. Parallelism:
    • Concurrency: Multiple tasks making progress over time by switching between them (even on a single core).
    • Parallelism: Multiple tasks executing simultaneously on multiple cores. Java’s threading model aims to achieve parallelism.

Creating Threads in Java

There are two primary ways to create threads in Java:

  • Implementing the Runnable Interface:
    
    class MyRunnable implements Runnable {
        private String threadName;
    
        public MyRunnable(String name) {
            threadName = name;
            System.out.println("Creating " +  threadName );
        }
    
        public void run() {
            System.out.println("Running " +  threadName );
            try {
                for(int i = 4; i > 0; i--) {
                    System.out.println("Thread: " + threadName + ", Count: " + i);
                    Thread.sleep(50);
                }
            } catch (InterruptedException e) {
                System.out.println("Thread " +  threadName + " interrupted.");
            }
            System.out.println("Thread " +  threadName + " exiting.");
        }
    }
    
    public class RunnableExample {
        public static void main(String args[]) {
            MyRunnable runnable1 = new MyRunnable( "Thread-1");
            Thread thread1 = new Thread(runnable1);
            thread1.start();
    
            MyRunnable runnable2 = new MyRunnable( "Thread-2");
            Thread thread2 = new Thread(runnable2);
            thread2.start();
        }
    }
                
  • Extending the Thread Class:
    
    class MyThread extends Thread {
        private String threadName;
    
        public MyThread(String name) {
            threadName = name;
            System.out.println("Creating " +  threadName );
        }
    
        public void run() {
            System.out.println("Running " +  threadName );
            try {
                for(int i = 4; i > 0; i--) {
                    System.out.println("Thread: " + threadName + ", Count: " + i);
                    Thread.sleep(50);
                }
            } catch (InterruptedException e) {
                System.out.println("Thread " +  threadName + " interrupted.");
            }
            System.out.println("Thread " +  threadName + " exiting.");
        }
    
        public static void main(String args[]) {
            MyThread thread1 = new MyThread( "Thread-1");
            thread1.start();
    
            MyThread thread2 = new MyThread( "Thread-2");
            thread2.start();
        }
    }
                

Implementing Runnable is generally preferred as it allows your class to extend another class if needed, promoting better code organization and adhering to the principle of favoring composition over inheritance for behavior reuse.

Thread Lifecycle

A thread in Java goes through various states during its lifecycle:

  • New: The thread has been created but not yet started.
  • Runnable: The thread is ready to run and is waiting for CPU time.
  • Running: The thread is currently executing.
  • Blocked: The thread is waiting for a monitor lock to enter a synchronized block/method or after calling wait().
  • Waiting: The thread is waiting indefinitely for another thread to perform a specific action (e.g., calling wait() without a timeout, join() without a timeout).
  • Timed Waiting: The thread is waiting for a specified amount of time (e.g., sleep(), wait(timeout), join(timeout)).
  • Terminated: The thread has finished its execution.

Synchronization

When multiple threads access shared resources, synchronization mechanisms are essential to prevent race conditions and ensure data integrity.

  • synchronized Keyword: Used to create synchronized blocks or methods. Only one thread can hold the monitor lock of an object at a time, preventing concurrent access to the synchronized code.
  • java.util.concurrent Package: Provides a rich set of utility classes for concurrent programming, including:
    • Locks (Lock, ReentrantLock, ReadWriteLock): More flexible locking mechanisms than the synchronized keyword.
    • Semaphores (Semaphore): Controls the number of threads that can access a resource concurrently.
    • CountDownLatch (CountDownLatch): Allows one or more threads to wait until a set of operations being performed in other threads has been completed.
    • CyclicBarrier (CyclicBarrier): Allows a set of threads to all wait for each other to reach a common barrier point.
    • Exchanger (Exchanger): Allows two threads to exchange objects.
    • Concurrent Collections (ConcurrentHashMap, ConcurrentLinkedQueue, etc.): Thread-safe collections that can be safely accessed and modified by multiple threads without explicit synchronization in many cases.
    • Executors (ExecutorService, ThreadPoolExecutor, ForkJoinPool): Framework for managing and executing threads, simplifying the creation and management of thread pools.

Example using synchronized


class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

public class SynchronizedExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i  {
            for (int i = 0; i 

The synchronized keyword in the increment() method ensures that only one thread can access and modify the count variable at a time, preventing race conditions.

Best Practices for Multi-Threaded Programming in Java

  • Minimize Shared Mutable State: Reduce the amount of mutable data shared between threads to minimize the need for complex synchronization. Favor immutable objects when possible.
  • Use Appropriate Synchronization Mechanisms: Choose the right synchronization tools (synchronized, Locks, Semaphores, Concurrent Collections, Executors) based on the specific concurrency requirements of your task.
  • Avoid Deadlocks: Be mindful of the order in which threads acquire locks to prevent deadlocks (a situation where two or more threads are blocked indefinitely, waiting for each other to release the locks they need).
  • Use Thread Pools (Executors): Instead of manually creating and managing threads, use the ExecutorService to manage a pool of reusable threads, improving performance and resource management.
  • Be Aware of Visibility Issues: Changes made by one thread to shared variables might not be immediately visible to other threads without proper synchronization (e.g., using volatile keyword for simple cases or proper locking).
  • Handle Exceptions Properly in Threads: Ensure that exceptions thrown within threads are caught and handled appropriately to prevent silent failures.
  • for Concurrency: Think about concurrency from the beginning of your design process, especially for performance-critical applications.
  • Test Thoroughly in Concurrent Environments: Testing multi-threaded code can be challenging. Use appropriate testing strategies to identify potential concurrency issues.

Conclusion

Java’s comprehensive support for multi-threading empowers developers to build high-performance and responsive applications in 2025. By understanding the fundamentals of threads, synchronization mechanisms, and best practices, you can effectively leverage concurrency to utilize modern multi-core processors and create robust and scalable software.

Agentic AI AI AI Agent Algorithm Algorithms API Automation Autonomous AWS Azure Career Chatbot cloud cpu database Databricks Data structure Design gcp Generative AI gpu interview java Kafka Life LLM LLMs Micro Services monitoring Monolith N8n Networking Optimization Platforms productivity python Q&A RAG redis Spark sql time series vector Vertex AI Workflow

Leave a Reply

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