Interview contents
Java Concurrency – Interview & Real-World Scenarios
This final part in our Java Concurrency series focuses on interview-specific pitfalls and real-world concurrency scenarios. You’ll learn how to avoid traps like deadlocks, design producer–consumer pipelines, and compare concurrency models.
1. Common Interview Pitfalls
Singleton and Concurrency
A common interview question: “How do you make a Singleton thread-safe in Java?”
- Incorrect (not thread-safe):
public class BadSingleton { private static BadSingleton instance; public static BadSingleton getInstance() { if (instance == null) { instance = new BadSingleton(); } return instance; }}- Thread-safe (synchronized method):
public class SafeSingleton { private static SafeSingleton instance; public static synchronized SafeSingleton getInstance() { if (instance == null) { instance = new SafeSingleton(); } return instance; }}- Better (double-checked locking with volatile):
public class DCLSingleton { private static volatile DCLSingleton instance; public static DCLSingleton getInstance() { if (instance == null) { synchronized (DCLSingleton.class) { if (instance == null) { instance = new DCLSingleton(); } } } return instance; }}Deadlock Avoidance
- Scenario: Two threads hold locks in opposite order.
- Fix: Always acquire locks in a consistent global order or use
tryLock()with timeout.
Other Interview Traps
- Misusing
volatileto guarantee atomicity (it doesn’t). - Assuming
HashMapis thread-safe (it’s not). - Forgetting to shut down an
ExecutorService.
2. Designing a Producer–Consumer Pipeline
A classic concurrency problem, common in interviews and real systems.
Example with BlockingQueue
import java.util.concurrent.*;
class ProducerConsumer { public static void main(String[] args) { BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
Runnable producer = () -> { try { for (int i = 0; i < 10; i++) { queue.put(i); System.out.println("Produced " + i); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } };
Runnable consumer = () -> { try { while (true) { int item = queue.take(); System.out.println("Consumed " + item); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } };
new Thread(producer).start(); new Thread(consumer).start(); }}- Why BlockingQueue?
- Handles synchronization internally.
- Blocks producers if full, consumers if empty.
- Avoids manual
wait()/notify()pitfalls.
3. Comparing Concurrency Models
Threads (Low-Level Model)
- Explicitly create and manage threads.
- Full control but more error-prone.
- Good for fine-grained concurrency, but harder to scale.
Async/Callback Model
- Uses event loops and futures.
- Example:
CompletableFuturein Java.
CompletableFuture.supplyAsync(() -> "data") .thenApply(String::toUpperCase) .thenAccept(System.out::println);- Great for I/O-bound tasks.
- Drawback: Callback hell if not structured well.
Actor Model
- Each actor encapsulates state and communicates via messages.
- Popularized by Akka (Java/Scala).
- Scales horizontally across cores/nodes.
- Great for highly concurrent, distributed systems.
- Tradeoff: Learning curve, debugging message-passing.
4. Best Practices for Interviews & Real Systems
- Always discuss tradeoffs (e.g.,
synchronizedvsLock). - Mention scalability and fairness in locks.
- For pipelines, suggest backpressure handling (bounded queues).
- Prefer immutable objects to reduce synchronization.
- Highlight higher-level abstractions (
ExecutorService,CompletableFuture,Streams).
5. Quick Interview Checklist
- Explain thread safety in Singleton.
- Avoid deadlocks → consistent lock ordering.
- Know BlockingQueue producer–consumer.
- Compare Thread vs Async vs Actor.
- Show knowledge of CompletableFuture for async flows.
Conclusion
Concurrency is not just about writing threads — it’s about designing safe, scalable systems. For interviews, focus on correctness, clarity, and tradeoffs. In real-world systems, use higher-level abstractions and proven concurrency models to reduce complexity.