Module 29 of 30 · Java Programming — Core to Enterprise · Intermediate

Multithreading & Concurrency

Duration: 7 min

Multithreading allows a program to execute multiple tasks concurrently. Java provides several mechanisms for managing threads safely, including synchronization, volatile variables, and high-level concurrency utilities like ExecutorService and CompletableFuture. Understanding thread safety is crucial for building robust concurrent applications.

Thread and Runnable

A thread is a lightweight process. You can create threads by extending Thread or implementing Runnable.

// Method 1: Extend Thread
class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

// Method 2: Implement Runnable
class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

public class ThreadBasics {
    public static void main(String[] args) {
        // Using Thread
        MyThread t1 = new MyThread();
        t1.start();
        
        // Using Runnable
        Thread t2 = new Thread(new MyRunnable());
        t2.start();
    }
}
Thread-0: 0
Thread-1: 0
Thread-0: 1
Thread-1: 1
Thread-0: 2
Thread-1: 2

Synchronized Methods and Blocks

Synchronization prevents race conditions by ensuring only one thread accesses a resource at a time.

class Counter {
    private int count = 0;
    
    // Synchronized method
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

public class SynchronizationExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        
        // Create multiple threads incrementing the counter
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("Final count: " + counter.getCount());
    }
}
Final count: 2000

Volatile Variables

The volatile keyword ensures that changes to a variable are visible to all threads immediately.

class VolatileFlag {
    private volatile boolean running = true;
    
    public void stop() {
        running = false;
    }
    
    public void work() {
        while (running) {
            // Do work
        }
        System.out.println("Work stopped");
    }
}

public class VolatileExample {
    public static void main(String[] args) throws InterruptedException {
        VolatileFlag flag = new VolatileFlag();
        
        Thread worker = new Thread(flag::work);
        worker.start();
        
        Thread.sleep(100);
        flag.stop();
        
        worker.join();
    }
}
Work stopped

ExecutorService

ExecutorService manages a pool of threads, making it easier to execute tasks concurrently.

import java.util.concurrent.*;

public class ExecutorServiceExample {
    public static void main(String[] args) throws Exception {
        // Create a thread pool with 3 threads
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // Submit tasks
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Task " + taskId + " running on " + 
                                   Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        
        // Shutdown executor
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println("All tasks completed");
    }
}
Task 0 running on pool-1-thread-1
Task 1 running on pool-1-thread-2
Task 2 running on pool-1-thread-3
Task 3 running on pool-1-thread-1
Task 4 running on pool-1-thread-2
All tasks completed

CompletableFuture

CompletableFuture provides a way to write asynchronous, non-blocking code with a fluent API.

import java.util.concurrent.*;

public class CompletableFutureExample {
    public static void main(String[] args) throws Exception {
        // Create a CompletableFuture
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Result from async task";
        });
        
        // Chain operations
        future.thenApply(result -> result.toUpperCase())
              .thenAccept(result -> System.out.println("Final result: " + result));
        
        // Wait for completion
        future.join();
    }
}
Final result: RESULT FROM ASYNC TASK

Thread Safety Patterns

Common patterns for ensuring thread safety in concurrent applications.

import java.util.concurrent.*;
import java.util.*;

public class ThreadSafetyPatterns {
    // Pattern 1: Immutable objects
    static class ImmutableData {
        private final String value;
        private final int number;
        
        public ImmutableData(String value, int number) {
            this.value = value;
            this.number = number;
        }
        
        public String getValue() { return value; }
        public int getNumber() { return number; }
    }
    
    // Pattern 2: Thread-safe collections
    static class SafeList {
        private List<String> list = Collections.synchronizedList(new ArrayList<>());
        
        public void add(String item) { list.add(item); }
        public List<String> getAll() { return new ArrayList<>(list); }
    }
    
    public static void main(String[] args) throws Exception {
        // Using immutable objects
        ImmutableData data = new ImmutableData("test", 42);
        System.out.println("Immutable: " + data.getValue());
        
        // Using thread-safe collections
        SafeList safeList = new SafeList();
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        for (int i = 0; i < 5; i++) {
            final int id = i;
            executor.submit(() -> safeList.add("Item " + id));
        }
        
        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.SECONDS);
        System.out.println("Safe list: " + safeList.getAll());
    }
}
Immutable: test
Safe list: [Item 0, Item 1, Item 2, Item 3, Item 4]

❓ What method must be called to start a thread?

❓ What does the synchronized keyword prevent?

❓ What does the volatile keyword ensure?

❓ What does ExecutorService manage?

❓ What is the main advantage of CompletableFuture?

Learn more: https://docs.oracle.com/javase/tutorial/essential/concurrency/

← Previous Continue interactively → Next →

Related Courses