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: 2Synchronized 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: 2000Volatile 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 stoppedExecutorService
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 completedCompletableFuture
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 TASKThread 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/