Java Multithreading

Multithreading enables concurrent execution of multiple threads in Java, allowing programs to perform multiple operations simultaneously.

What is a Thread?

A thread is the smallest unit of execution within a process. Java programs can have multiple threads running concurrently.

Creating Threads

Method 1: Extending Thread Class

1class MyThread extends Thread { 2 public void run() { 3 System.out.println("Thread running: " + Thread.currentThread().getName()); 4 } 5} 6 7// Usage 8MyThread thread = new MyThread(); 9thread.start();

Method 2: Implementing Runnable Interface (Preferred)

1class MyTask implements Runnable { 2 public void run() { 3 System.out.println("Task executing: " + Thread.currentThread().getName()); 4 } 5} 6 7// Usage 8Thread thread = new Thread(new MyTask()); 9thread.start();

Method 3: Using Lambda Expressions

1Thread thread = new Thread(() -> { 2 System.out.println("Lambda thread: " + Thread.currentThread().getName()); 3}); 4thread.start();

Thread Lifecycle

  1. NEW: Thread created but not started
  2. RUNNABLE: Ready to run or running
  3. BLOCKED: Waiting for monitor lock
  4. WAITING: Waiting indefinitely
  5. TIMED_WAITING: Waiting for specified time
  6. TERMINATED: Thread execution completed
1Thread thread = new Thread(() -> { 2 try { 3 Thread.sleep(1000); // TIMED_WAITING 4 } catch (InterruptedException e) { 5 e.printStackTrace(); 6 } 7}); 8 9System.out.println(thread.getState()); // NEW 10thread.start(); 11System.out.println(thread.getState()); // RUNNABLE

Synchronization

The Problem: Race Conditions

1class Counter { 2 private int count = 0; 3 4 public void increment() { 5 count++; // Not thread-safe! 6 } 7 8 public int getCount() { 9 return count; 10 } 11}

Solution: Synchronized Methods

1class ThreadSafeCounter { 2 private int count = 0; 3 4 public synchronized void increment() { 5 count++; // Thread-safe 6 } 7 8 public synchronized int getCount() { 9 return count; 10 } 11}

Synchronized Blocks

1class BankAccount { 2 private double balance; 3 4 public void withdraw(double amount) { 5 synchronized(this) { // Lock on this object 6 if (balance >= amount) { 7 balance -= amount; 8 } 9 } 10 } 11}

Thread Communication

wait() and notify()

1class ProducerConsumer { 2 private Queue<Integer> queue = new LinkedList<>(); 3 private int capacity = 5; 4 5 public void produce(int item) throws InterruptedException { 6 synchronized(this) { 7 while (queue.size() == capacity) { 8 wait(); // Wait for consumer 9 } 10 queue.add(item); 11 notify(); // Notify consumer 12 } 13 } 14 15 public int consume() throws InterruptedException { 16 synchronized(this) { 17 while (queue.isEmpty()) { 18 wait(); // Wait for producer 19 } 20 int item = queue.remove(); 21 notify(); // Notify producer 22 return item; 23 } 24 } 25}

Concurrency Utilities

ExecutorService

1ExecutorService executor = Executors.newFixedThreadPool(5); 2 3executor.submit(() -> { 4 System.out.println("Task 1"); 5}); 6 7executor.submit(() -> { 8 System.out.println("Task 2"); 9}); 10 11executor.shutdown(); // Important: shutdown the executor

Future and Callable

1ExecutorService executor = Executors.newSingleThreadExecutor(); 2 3Future<Integer> future = executor.submit(() -> { 4 Thread.sleep(1000); 5 return 42; 6}); 7 8// Do other work while task runs 9System.out.println("Doing other work..."); 10 11// Get result (blocks if not ready) 12Integer result = future.get(); 13System.out.println("Result: " + result); 14 15executor.shutdown();

Thread Pools

Fixed Thread Pool

1ExecutorService executor = Executors.newFixedThreadPool(4); 2// Always 4 threads available

Cached Thread Pool

1ExecutorService executor = Executors.newCachedThreadPool(); 2// Creates threads as needed, reuses idle threads

Single Thread Executor

1ExecutorService executor = Executors.newSingleThreadExecutor(); 2// Only one thread at a time

Common Issues

Deadlocks

1// Avoid this pattern! 2public void method1() { 3 synchronized(lock1) { 4 synchronized(lock2) { 5 // Do work 6 } 7 } 8} 9 10public void method2() { 11 synchronized(lock2) { 12 synchronized(lock1) { 13 // Do work 14 } 15 } 16}

Starvation

Low-priority threads may never get CPU time.

Livelock

Threads keep responding to each other but make no progress.

Best Practices

  1. Use ExecutorService instead of creating threads directly
  2. Prefer immutable objects when possible
  3. Use concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList)
  4. Minimize synchronized scope
  5. Always shutdown ExecutorService
  6. Handle InterruptedException properly
  7. Use volatile for variables accessed by multiple threads

Modern Java Concurrency

Java 8+ introduced many improvements:

  • CompletableFuture for asynchronous programming
  • Parallel streams for data processing
  • Improved atomic classes
  • Lock interface for flexible locking

Multithreading is essential for building responsive and scalable Java applications, especially in modern multi-core systems.