Multithreading and Concurrency Questions
How do you create a thread in Java?
- Extend
Threadand overriderun(). - Implement
Runnable(preferred, the class can still extend something else) and pass it tonew Thread(r). - Implement
Callable<V>and submit it to anExecutorServiceto get aFuturewith a result. - Java 21:
Thread.ofVirtual().start(r)orExecutors.newVirtualThreadPerTaskExecutor().
Note: Call start(), not run(). run() just executes the method on the current thread.
What are the states of a thread?
NEW ──start()──▶ RUNNABLE ──run() ends──▶ TERMINATED
│ ▲
waiting for a │ │ lock acquired / notified / time over
lock, wait(), ▼ │
sleep(), join() BLOCKED / WAITING / TIMED_WAITINGWhat is synchronization and why is it needed?
When threads share mutable data, operations like count++ (read, add, write) can interleave and lose updates (a race condition). synchronized lets only one thread at a time hold an object's monitor lock, and it also guarantees visibility of changes.
Example
Two threads incrementing 10,000 times each with a synchronized method always give 20,000.
public class Main {
private int count = 0;
synchronized void increment() { count++; }
public static void main(String[] args) throws InterruptedException {
Main counter = new Main();
Runnable task = () -> {
for (int i = 0; i < 10_000; i++) counter.increment();
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Count = " + counter.count);
}
}Output
Count = 20000
What does volatile do? Is it enough for count++?
volatile guarantees visibility: every read sees the latest write, and it prevents reordering around the variable. It does not make compound actions atomic, so volatile int count; count++ is still unsafe. Use it for flags (volatile boolean running); use AtomicInteger or locks for counters.
sleep() vs wait()?
| sleep() | wait() | |
|---|---|---|
| Class | Thread (static) | Object |
| Releases lock | no | yes |
| Called from | anywhere | inside synchronized on the same object |
| Wakes up | after the time | notify()/notifyAll() or timeout |
What is a deadlock and how do you prevent it?
A deadlock happens when two or more threads each hold a lock and wait forever for a lock held by the other. Example: T1 locks A then wants B, T2 locks B then wants A.
- Always acquire locks in the same global order.
- Use
ReentrantLock.tryLock(timeout)instead of blocking forever. - Keep synchronized blocks small and avoid calling unknown code while holding a lock.
- Detect with a thread dump (
jstack <pid>), which reports "Found one Java-level deadlock".
What is ExecutorService and why use a thread pool?
Creating a thread per task is expensive and unbounded. An ExecutorService reuses a fixed set of worker threads and queues tasks. Common factories: newFixedThreadPool(n), newCachedThreadPool(), newSingleThreadExecutor(), newScheduledThreadPool(n). Always call shutdown().
Example
Submitting Callables and reading Futures in order.
import java.util.*;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(3);
List<Future<Integer>> results = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
int n = i;
results.add(pool.submit(() -> n * n));
}
for (Future<Integer> f : results) {
System.out.println("Result: " + f.get());
}
pool.shutdown();
}
}Output
Result: 1 Result: 4 Result: 9 Result: 16
Runnable vs Callable?
Runnable.run() returns nothing and cannot throw checked exceptions. Callable.call() returns a value and can throw checked exceptions. Submitting a Callable to an executor returns a Future<V> whose get() waits for the result.
What is CompletableFuture?
A Future you can compose without blocking: supplyAsync, then thenApply / thenCompose / thenCombine, and handle errors with exceptionally / handle. allOf waits for many tasks. Used to call several services in parallel.
Example
Two "service calls" run in parallel and are combined.
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture<String> user = CompletableFuture.supplyAsync(() -> "Ravi");
CompletableFuture<Integer> orders = CompletableFuture.supplyAsync(() -> 7);
String summary = user
.thenCombine(orders, (u, o) -> u + " has " + o + " orders")
.exceptionally(ex -> "fallback")
.join();
System.out.println(summary);
}
}Output
Ravi has 7 orders
synchronized vs ReentrantLock?
| synchronized | ReentrantLock | |
|---|---|---|
| Unlock | automatic at block end | manual unlock() in finally |
| tryLock / timeout | no | yes |
| Interruptible wait | no | lockInterruptibly() |
| Fairness | no | optional fair mode |
| Conditions | one (wait/notify) | many Condition objects |
What are virtual threads (Java 21)?
Lightweight threads managed by the JVM, not the OS. Millions can exist; when one blocks on I/O, the JVM unmounts it from its carrier (platform) thread so the carrier serves others. They make simple blocking "thread-per-request" code scale like async code. Create with Thread.ofVirtual().start(...) or Executors.newVirtualThreadPerTaskExecutor(). Do not pool them; they help I/O-bound, not CPU-bound, work.