Top Java Multithreading Q&A
Top Java Multithreading Q&A
in Java. Each example is accompanied by a brief explanation and a complete code snippet.
Creates a fixed-size thread pool, submits a task, and shuts down the pool.
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> {
System.out.println("Hello from ExecutorService");
});
executor.shutdown();
}
}
Explanation:
A fixed thread pool of 3 threads is created. A simple task is submitted, and then the executor is gracefully shut down.
Submits a Callable task that returns a value and waits for the result.
java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 123;
};
Future<Integer> future = executor.submit(task);
try {
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
1
}
}
Explanation:
This example demonstrates using a Callable to return a value. The Future is used to wait for and retrieve the
computed result.
Shows how to gracefully shut down an ExecutorService and wait for tasks to finish.
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ShutdownExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> System.out.println("Task running"));
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
Explanation:
After submitting tasks, the code waits for termination. If tasks don't complete within 5 seconds, it forces shutdown.
java
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> {
System.out.println("Delayed task executed");
}, 3, TimeUnit.SECONDS);
scheduler.shutdown();
2
}
}
Explanation:
A scheduled task is executed after a 3-second delay using a single-threaded scheduler.
java
import java.util.concurrent.*;
public class CancelTaskExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<?> future = executor.submit(() -> {
try {
while (true) {
System.out.println("Running task...");
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Task interrupted");
}
});
Thread.sleep(2000);
System.out.println("Cancelling task...");
future.cancel(true);
executor.shutdown();
}
}
Explanation:
A continuous task is started and, after 2 seconds, it is canceled by calling future.cancel(true), interrupting the
running thread.
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello from
CompletableFuture");
3
try {
String result = future.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
Explanation:
A task is executed asynchronously with supplyAsync, returning a string that is retrieved using get().
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureChainExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5)
.thenApply(n -> n * 2) // Multiply by 2
.thenCompose(result -> CompletableFuture.supplyAsync(() -> result + 10)); //
Add 10 asynchronously
System.out.println("Result: " + future.get()); // Expected: (5 * 2) + 10 = 20
}
}
Explanation:
The first stage supplies a number, the second stage doubles it, and the third stage adds 10 asynchronously using
thenCompose.
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureCombineExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 22);
CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2,
Integer::sum);
System.out.println("Combined result: " + combinedFuture.get()); // Expected: 42
4
}
}
Explanation:
Two asynchronous tasks are run independently, and their results are combined using thenCombine with a sum
operation.
java
import java.util.concurrent.CompletableFuture;
public class CompletableFutureAllOfExample {
public static void main(String[] args) {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task3");
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2,
future3);
allFutures.join(); // Wait until all tasks complete
try {
System.out.println("Future1: " + future1.get());
System.out.println("Future2: " + future2.get());
System.out.println("Future3: " + future3.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Explanation:
CompletableFuture.allOf is used to combine multiple futures. The program waits until all futures are complete
before retrieving individual results.
java
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExceptionExample {
public static void main(String[] args) {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Something went wrong!");
5
}
return 10;
}).exceptionally(ex -> {
System.out.println("Exception: " + ex.getMessage());
return 0;
});
System.out.println("Result: " + future.join()); // Expected output: 0
}
}
Explanation:
If the asynchronous task fails, the exceptionally block catches the exception and returns a fallback value (0).
These 10 examples cover a broad range of common patterns and techniques using ExecutorService and
CompletableFuture. They provide a solid foundation for building robust concurrent applications in Java.
● Race Condition: Occurs when multiple threads access shared data concurrently and at least one thread
modifies it, leading to unpredictable results.
● Deadlock: Happens when two or more threads are waiting for each other to release locks, causing the
threads to be blocked indefinitely.
○ Prevention:
Q6: Implement a thread-safe counter using two approaches. Explain the trade-offs.
java
6
}
public synchronized int getCount() {
return count;
}
}
Explanation:
This approach ensures that only one thread can execute either method at a time. It’s simple and effective, but may
suffer from performance bottlenecks when many threads contend for the same lock.
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
Explanation:
Atomic variables use low-level CPU instructions to perform atomic operations, reducing the overhead of locks. This
method scales better under high contention but is limited to specific atomic operations.
java
7
// Returns the current count in a thread-safe manner
public synchronized int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
// Create multiple threads to increment 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("Synchronized Counter Value: " + counter.getCount());
}
}
2. Using AtomicInteger
This version leverages low-level CPU instructions for atomicity, which minimizes the overhead of explicit
synchronization.
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
// Increments the counter atomically
public void increment() {
count.incrementAndGet();
}
// Retrieves the current value atomically
public int getCount() {
return count.get();
}
public static void main(String[] args) throws InterruptedException {
AtomicCounter counter = new AtomicCounter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
8
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("Atomic Counter Value: " + counter.getCount());
}
}
Solution:
java
import java.util.LinkedList;
import java.util.Queue;
public class BoundedBuffer<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
public BoundedBuffer(int capacity) {
this.capacity = capacity;
}
// Producer method: waits if the buffer is full
public synchronized void put(T item) throws InterruptedException {
while (queue.size() == capacity) {
wait();
}
queue.add(item);
notifyAll(); // Notify consumers that an item is available
}
// Consumer method: waits if the buffer is empty
public synchronized T take() throws InterruptedException {
9
while (queue.isEmpty()) {
wait();
}
T item = queue.poll();
notifyAll(); // Notify producers that space is available
return item;
}
public static void main(String[] args) {
BoundedBuffer<Integer> buffer = new BoundedBuffer<>(5);
// Producer thread
Thread producer = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
buffer.put(i);
System.out.println("Produced: " + i);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
// Consumer thread
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
int value = buffer.take();
System.out.println("Consumed: " + value);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
producer.start();
consumer.start();
}
}
Solution:
java
10
public EvenOddPrinter(int limit) {
this.limit = limit;
}
// Method for printing odd numbers
public synchronized void printOdd() {
while (count <= limit) {
while (count % 2 == 0) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (count <= limit) {
System.out.println("Odd: " + count);
count++;
notifyAll();
}
}
}
// Method for printing even numbers
public synchronized void printEven() {
while (count <= limit) {
while (count % 2 != 0) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (count <= limit) {
System.out.println("Even: " + count);
count++;
notifyAll();
}
}
}
public static void main(String[] args) {
int limit = 10;
EvenOddPrinter printer = new EvenOddPrinter(limit);
Thread oddThread = new Thread(printer::printOdd);
Thread evenThread = new Thread(printer::printEven);
oddThread.start();
evenThread.start();
}
}
11
Solution:
java
These examples and exercises cover a range of multithreading topics from synchronization and atomic operations to
classic concurrency problems. Use these examples to practice your coding skills and deepen your understanding of
Java concurrency concepts.
You said:
ChatGPT said:
Below are three additional multithreading challenges with complete solutions, each addressing a distinct aspect of
concurrent programming in Java.
12
Problem:
Simulate the Dining Philosophers problem while avoiding deadlock. Philosophers alternate between thinking and
eating, but each must acquire two forks (shared resources) to eat. Use a strategy (for example, alternating fork
acquisition order) to prevent deadlock.
Solution:
java
import java.util.concurrent.locks.ReentrantLock;
class Philosopher extends Thread {
private final int id;
private final ReentrantLock leftFork;
private final ReentrantLock rightFork;
public Philosopher(int id, ReentrantLock leftFork, ReentrantLock rightFork) {
this.id = id;
this.leftFork = leftFork;
this.rightFork = rightFork;
}
@Override
public void run() {
try {
while (true) {
think();
// Even-numbered philosophers pick left fork first; odd-numbered pick right
fork first
if (id % 2 == 0) {
leftFork.lock();
rightFork.lock();
} else {
rightFork.lock();
leftFork.lock();
}
eat();
// Release locks
leftFork.unlock();
rightFork.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void think() throws InterruptedException {
System.out.println("Philosopher " + id + " is thinking.");
Thread.sleep((int)(Math.random() * 100));
}
private void eat() throws InterruptedException {
System.out.println("Philosopher " + id + " is eating.");
Thread.sleep((int)(Math.random() * 100));
}
}
public class DiningPhilosophers {
public static void main(String[] args) {
13
int numPhilosophers = 5;
ReentrantLock[] forks = new ReentrantLock[numPhilosophers];
for (int i = 0; i < numPhilosophers; i++) {
forks[i] = new ReentrantLock();
}
Philosopher[] philosophers = new Philosopher[numPhilosophers];
for (int i = 0; i < numPhilosophers; i++) {
ReentrantLock leftFork = forks[i];
ReentrantLock rightFork = forks[(i + 1) % numPhilosophers];
philosophers[i] = new Philosopher(i, leftFork, rightFork);
philosophers[i].start();
}
}
}
Explanation:
This solution uses a simple strategy: even and odd philosophers acquire forks in a different order, preventing circular
waiting and thus avoiding deadlock.
Solution:
java
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class SimpleThreadPool {
private final BlockingQueue<Runnable> taskQueue;
private final WorkerThread[] workers;
private volatile boolean isStopped = false;
public SimpleThreadPool(int numThreads) {
taskQueue = new LinkedBlockingQueue<>();
workers = new WorkerThread[numThreads];
for (int i = 0; i < numThreads; i++) {
workers[i] = new WorkerThread(taskQueue);
workers[i].start();
}
}
public void execute(Runnable task) {
if (isStopped) {
throw new IllegalStateException("ThreadPool is stopped");
}
14
taskQueue.offer(task);
}
public void shutdown() {
isStopped = true;
for (WorkerThread worker : workers) {
worker.interrupt();
}
}
private static class WorkerThread extends Thread {
private final BlockingQueue<Runnable> taskQueue;
public WorkerThread(BlockingQueue<Runnable> queue) {
this.taskQueue = queue;
}
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
Runnable task = taskQueue.take();
task.run();
}
} catch (InterruptedException e) {
// Thread is interrupted; exit gracefully
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
SimpleThreadPool threadPool = new SimpleThreadPool(3);
for (int i = 1; i <= 10; i++) {
int taskNumber = i;
threadPool.execute(() -> {
System.out.println("Executing task " + taskNumber + " by " +
Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Allow tasks to complete before shutting down
Thread.sleep(5000);
threadPool.shutdown();
}
}
Explanation:
This thread pool creates a fixed number of worker threads that continuously take tasks from a
LinkedBlockingQueue. The pool supports graceful shutdown by interrupting the workers.
15
Question 6: Barrier Synchronization Using CyclicBarrier
Problem:
Use a CyclicBarrier to synchronize a group of threads. All threads should perform a task and then wait at the
barrier until all have reached it before continuing.
Solution:
java
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class BarrierExample {
public static void main(String[] args) {
int parties = 5;
CyclicBarrier barrier = new CyclicBarrier(parties,
() -> System.out.println("All threads reached the barrier. Continuing
execution..."));
for (int i = 0; i < parties; i++) {
new Thread(new Task(barrier), "Thread-" + i).start();
}
}
}
class Task implements Runnable {
private final CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is performing part 1 of
the task.");
Thread.sleep((int) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " is waiting at the
barrier.");
barrier.await(); // Wait until all threads reach this point
// After barrier is tripped, continue with further tasks
System.out.println(Thread.currentThread().getName() + " resumed execution after
barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
Explanation:
In this example, five threads perform some work and then call barrier.await(). Only when all threads have
reached the barrier does the barrier action execute (printing a message), and all threads continue their execution.
16