A team ships a lazy singleton using double-checked locking, but the `instance` field is a plain (non-volatile) `Object`. Under load on a multi-core JVM, another thread occasionally sees a non-null `instance` whose fields are still at their default values. What is the precise root cause?
- A. The compiler removes the inner null check via lock elision, so two threads run the constructor concurrently
- B. Without volatile, the write publishing the reference can be reordered before the constructor's field writes complete, so a reader sees a non-null but partially-constructed object ✓
- C. The synchronized block only guarantees mutual exclusion, so the second thread always re-runs the constructor and overwrites the first instance
- D. HotSpot never caches object references in registers, so the issue is purely a heap-visibility flush that a memory barrier on read would fix
Correct answer: B. Without volatile there is no happens-before edge, so the reference publication can be reordered ahead of the constructor's writes, exposing a partially-constructed object to another thread.
Consider `private volatile int[] data = new int[10];` accessed by many threads that do `data[i]++` concurrently on distinct-looking but sometimes overlapping indices. Why does marking the array reference `volatile` fail to make these updates thread-safe?
- A. volatile only establishes visibility/ordering for reads and writes of the reference itself, not for reads/writes of the array's elements, which remain unsynchronized ✓
- B. volatile arrays are copied on every write, so the increment operates on a stale snapshot
- C. volatile forces every element access through a lock, but the lock is reentrant so nested increments corrupt the counter
- D. The JIT strips volatile from array fields during escape analysis, silently downgrading it to a plain field
Correct answer: A. The volatile modifier applies to the reference variable, not the elements; per-element mutations have no memory-model guarantees and the `++` is also non-atomic.
Two threads run this code with shared `int x = 0; boolean ready = false;` — Thread A: `x = 42; ready = true;` — Thread B: `if (ready) System.out.println(x);`. Neither variable is volatile or synchronized. Which statement about permitted outcomes is correct under the JMM?
- A. B can only ever print 42, because assignment order in A is preserved by program order across threads
- B. B can print 0 or observe ready==true before x==42, because without a happens-before relation A's writes may be observed out of order ✓
- C. The program is guaranteed to deadlock since ready is read without a lock
- D. B will always block until ready becomes true because reads of shared booleans are implicitly synchronized
Correct answer: B. Absent a happens-before edge, Thread B may observe `ready==true` while still reading the stale `x==0` due to permitted reordering/visibility gaps.
You have a producer-consumer using `wait()`/`notify()`. A reviewer insists the consumer's `wait()` must sit inside `while (queue.isEmpty())` rather than `if (queue.isEmpty())`. What is the strongest technical justification?
- A. `if` would hold the monitor during wait, whereas `while` releases it, preventing a deadlock
- B. A thread returning from wait may find the condition false again due to spurious wakeups or another consumer having drained the item, so the predicate must be re-checked in a loop ✓
- C. `notify()` only wakes threads that used `while`, so `if`-based waiters are never scheduled
- D. `while` guarantees FIFO fairness among waiters, which `if` cannot provide
Correct answer: B. wait() can return without the intended condition holding (spurious wakeups, or another thread consumed the item), so the guard must be re-tested in a loop.
A `@Service` bean has a public `outer()` method (no annotation) that calls `this.inner()`, where `inner()` is annotated `@Transactional(propagation = REQUIRES_NEW)`. At runtime `inner()` runs in no transaction at all. Why?
- A. REQUIRES_NEW is ignored unless the outer method is also @Transactional, so propagation silently downgrades to SUPPORTS
- B. The self-invocation `this.inner()` bypasses the Spring CGLIB/JDK proxy, so the transactional advice around inner() never fires ✓
- C. REQUIRES_NEW requires an XA datasource; without one Spring skips transaction creation and logs a warning
- D. Spring caches the transaction status per thread, and since outer() opened none, inner() inherits the empty context permanently
Correct answer: B. Transactional advice lives on the proxy; an internal `this.` call goes straight to the target instance, skipping the proxy and thus all propagation handling.
A production service running Hibernate returns a `User` entity from a `@Transactional` service method to a controller, which then accesses `user.getOrders()` (a `LAZY` collection) to render JSON. Users get intermittent `LazyInitializationException`. What is the correct root-cause explanation?
- A. The collection was mapped EAGER, so Hibernate throws when it detects a redundant fetch in the view layer
- B. The persistence context/session closed when the transactional method returned, so the proxy has no session to initialize the collection during controller-layer access ✓
- C. The exception occurs because getOrders() triggers an N+1 query that exceeds the connection pool, timing out the fetch
- D. Jackson serializes the collection on a different thread that lacks the ThreadLocal session, unlike single-threaded access which succeeds
Correct answer: B. Lazy collections need an open session to initialize; once the transactional boundary closes the session, touching the uninitialized proxy later throws LazyInitializationException.
A REST endpoint calls a downstream payment API. On timeout your client retries. Occasionally a customer is charged twice. Which approach most directly prevents duplicate side effects while keeping at-least-once retry semantics?
- A. Switch retries to at-most-once by disabling them entirely, accepting occasional lost charges
- B. Attach a client-generated idempotency key to each logical request so the server deduplicates retries of the same operation ✓
- C. Wrap the call in a distributed lock so only one node can call the payment API at a time
- D. Increase the client socket timeout so responses always arrive before a retry is triggered
Correct answer: B. An idempotency key lets the server recognize a retried request as the same logical operation and return the original result instead of charging again.
You must reliably publish an event to Kafka whenever a row is written to your SQL database, but a crash between the DB commit and the Kafka send can lose events (dual-write problem). Which pattern correctly guarantees the event is not lost?
- A. Send to Kafka first, then commit the DB row, so the broker is the source of truth
- B. Use the Outbox pattern: write the event into an outbox table in the same DB transaction, then a separate relay reads and publishes it, retrying until acknowledged ✓
- C. Wrap both the DB commit and the Kafka producer.send() in a single synchronized block
- D. Enable Kafka idempotent producer, which retroactively rolls back the DB row if the send fails
Correct answer: B. The Outbox pattern makes the event write atomic with the business data (one DB transaction), and a separate relay guarantees at-least-once delivery to Kafka.
A latency-sensitive service (P99 pause budget ~10ms) runs on a 64GB heap and suffers multi-hundred-millisecond stop-the-world pauses during full GCs with the Parallel collector. Which change best fits the requirement, and why?
- A. Switch to ZGC, because it performs concurrent marking and relocation to keep pauses sub-millisecond largely independent of heap size ✓
- B. Switch to the Serial collector to reduce GC thread contention on the large heap
- C. Keep Parallel GC but set -XX:MaxGCPauseMillis=10, which converts full GCs into concurrent cycles
- D. Shrink the heap to 4GB so full GCs complete within the 10ms budget
Correct answer: A. ZGC is designed for low, near-constant pause times that scale to large heaps by doing marking and relocation concurrently, matching a tight P99 pause goal.
A mutable object is used as a key in a `HashMap`. After insertion, code mutates a field that participates in `equals`/`hashCode`. Later `map.get(sameKeyReference)` returns null even though the entry is clearly present. What is the precise mechanism?
- A. HashMap rehashes on every mutation, so the entry was moved to a bucket the key no longer maps to
- B. The entry was stored in the bucket derived from the original hashCode; the mutated key now hashes to a different bucket, so the lookup probes the wrong bucket and misses the entry ✓
- C. equals() now returns false against itself, so identity comparison inside get() fails
- D. Treeification converted the bucket to a red-black tree keyed by the old hash, corrupting the lookup index
Correct answer: B. The entry stays in the bucket chosen at insertion time; a mutated hashCode routes get() to a different bucket, so it never finds the entry.