This is a lot to hold in your head at once.
Structure your answer around the evolution of Java concurrency: start with low-level primitives (threads, synchronized), then move to higher-level abstractions (Executor, locks, atomics, concurrent collections), and finish with asynchronous composition (CompletableFuture). For each, briefly explain what it solves and when to use it, emphasizing trade-offs like simplicity vs. control and blocking vs. non-blocking.
Pro tip: Tie your choices to real-world scenarios—e.g., 'For a high-throughput trading system, I'd use a bounded thread pool with a rejection policy and CompletableFuture for async orchestration to avoid blocking.' This shows you think about production constraints, not just APIs.
Explain how you create threads (Runnable, Thread) and the need for synchronization to prevent race conditions. Mention synchronized blocks/methods and their intrinsic lock behavior, including limitations like no timeout or interruptibility.
Describe how Executors decouple task submission from execution, covering thread pools (fixed, cached, scheduled), and the importance of choosing the right pool size and queue. Highlight lifecycle management with shutdown/awaitTermination.
Compare ReentrantLock with synchronized (tryLock, fairness, condition variables) and explain when to use them. Introduce atomic variables (AtomicInteger, LongAdder) for lock-free, thread-safe operations on single variables.
Mention ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue implementations, and how they provide thread-safe operations without external synchronization. Explain their internal mechanisms (e.g., CAS, segment locking) and appropriate use cases.
Explain Future for asynchronous results and CompletableFuture for composable, non-blocking pipelines (thenApply, thenCompose, exceptionally). Discuss how to combine multiple async operations and handle errors.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Deadlock prevention I handled okay, talked through lock ordering and using tryLock.
Start by defining the three problems and their root causes, then present a layered strategy: use high-level concurrency utilities (java.util.concurrent) to avoid manual synchronization, apply lock ordering and timeouts to prevent deadlocks, and use fair locks or work-stealing to prevent starvation. For each, give a concrete Java example, such as using ReentrantLock with tryLock for deadlock avoidance or AtomicInteger for race-free counters.
Pro tip: Emphasize that prevention is better than cure: prefer immutability and thread confinement to eliminate shared mutable state, which inherently avoids all three issues. Mention that you profile with tools like JStack or ThreadMXBean to detect deadlocks in production, showing a proactive mindset.
Briefly explain race conditions (uncoordinated access to shared data), deadlocks (circular waiting for locks), and starvation (threads indefinitely denied resources). This sets a clear foundation.
Use synchronization primitives like synchronized blocks, ReentrantLock, or atomic variables (AtomicInteger). Prefer immutable objects and thread-local storage to avoid shared state. Example: AtomicInteger for a counter instead of int++.
Establish a global lock ordering, use tryLock with timeouts, and avoid nested locks. Example: always acquire locks in the same order (e.g., by object ID) or use a single lock for multiple resources.
Use fair locks (new ReentrantLock(true)) or work-stealing algorithms (ForkJoinPool). Ensure threads have bounded waiting times and avoid priority inversion. Example: fair lock ensures FIFO acquisition.
Use tools like JStack, ThreadMXBean, or stress tests to detect deadlocks and contention. Mention that you write unit tests with multiple threads to validate concurrency behavior.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.