Control flow- CounterService has a private int counter initialized to 0.- increment() reads counter, adds 1, and writes it back.- getAndReset() reads counter into local, sets counter = 0, returns the saved value.Thread-safety issue / race conditions- counter is a plain int; increment() and getAndReset() are non-atomic composite operations. Concurrent threads can interleave: - Lost increments: two threads executing counter += 1 can read same value and write same result. - Race on reset: getAndReset() may read a stale or partial value while increments are happening; increments can be lost if they occur concurrently with the reset.- No visibility guarantees: without synchronization or volatility, one thread’s write may not be visible to another.Two possible fixes1) Use synchronization (simple, correct)java
public class CounterService {
private int counter = 0;
public synchronized void increment() {
counter += 1;
}
public synchronized int getAndReset() {
int current = counter;
counter = 0;
return current;
}
}
- Reasoning: synchronized provides atomicity and visibility; both methods are mutually exclusive so no interleaving.- Trade-off: simple but serializes all accesses; may be a bottleneck under high contention.2) Use AtomicInteger for lock-free atomic get-and-setjava
import java.util.concurrent.atomic.AtomicInteger;
public class CounterService {
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // atomic increment
}
public int getAndReset() {
return counter.getAndSet(0); // atomically returns old value and sets to 0
}
}
- Reasoning: incrementAndGet and getAndSet are atomic; getAndSet prevents lost increments around reset.- Trade-off: good performance under contention. Note: if you need to accumulate extremely high-volume concurrent updates, consider LongAdder for throughput; but LongAdder lacks a single atomic get-and-reset method (you can use sumThenReset() in Java 8+ which is atomic for LongAdder).Alternative (high-throughput)java
import java.util.concurrent.atomic.LongAdder;
public class CounterService {
private final LongAdder counter = new LongAdder();
public void increment() {
counter.increment();
}
public long getAndReset() {
return counter.sumThenReset(); // atomic for LongAdder
}
}
Complexity- Time: O(1) per operation.- Space: O(1).Edge cases and notes- Ensure getAndReset semantic: callers expect the returned value to include all increments up to the call moment; use AtomicInteger.getAndSet or LongAdder.sumThenReset to satisfy this.- If stronger ordering/consistency with other state is required, use external synchronization.Documentation guidance (what to write in class javadoc / README)- Explain concurrency contract explicitly: - "Thread-safe. increment() may be called concurrently. getAndReset() atomically returns the cumulative count up to invocation and resets the counter to zero. Implemented using AtomicInteger (or LongAdder) to guarantee atomicity and visibility."- Note performance characteristics: - "AtomicInteger: low contention OK; synchronized: simple but blocks. LongAdder: optimized for high update throughput; sumThenReset used for atomic reset."- Usage examples and expectations: - Show example sequence and promise (e.g., increments concurrent with getAndReset will either be included or not depending on ordering but no increments are lost).- Testing guidance: - Add multi-threaded unit tests simulating concurrent increments + resets (e.g., use ExecutorService and latches) and property checks (total increments == sum of all returned values).- Mention alternatives and when to choose each implementation.