Skip to content

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):
java
public class BadSingleton {
    private static BadSingleton instance;
    public static BadSingleton getInstance() {
        if (instance == null) {
            instance = new BadSingleton();
        }
        return instance;
    }
}
  • Thread-safe (synchronized method):
java
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):
java
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 volatile to guarantee atomicity (it doesn’t).
  • Assuming HashMap is 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

java
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: CompletableFuture in Java.
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., synchronized vs Lock).
  • 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.