Coding problem · Medium · Async & Concurrency
Thread-Safe Counting with ExecutorService
Medium Language practicals Seen at Amazon, Uber, LinkedIn, Goldman Sachs
Implement two classes:
HitCounter—increment()andget()that stay correct when called from many threads at once (no lost updates).ParallelSum.sum(int[] numbers, int threads)— split the array intothreadschunks, sum each chunk on anExecutorService, and return the total as along. Shut the pool down when done.
Hints
Hint 1
count++ on a plain field is a read-modify-write race. AtomicLong or LongAdder fixes it.
Hint 2
executor.invokeAll(tasks) runs Callables and returns Futures to get().
Write and run your solution
Loading the editor… (JavaScript is required to run code in your browser.)
Solution and explanation
Show the solution
Time O(n / threads) per worker · Space O(threads)
count++ compiles to read, add, write — two threads can read the same value and one increment is lost. AtomicLong.incrementAndGet() uses compare-and-swap; LongAdder spreads contention across cells and is faster under heavy writes (reads sum the cells). For ParallelSum, each task sums its own slice into a local variable — no shared mutable state — and the results are combined after invokeAll. Always shut the pool down (try/finally), or use try-with-resources on ExecutorService in Java 21.
Also mention that IntStream.of(numbers).parallel().asLongStream().sum() does this with the common fork-join pool.
Reference solution
class HitCounter {
private final java.util.concurrent.atomic.LongAdder count = new java.util.concurrent.atomic.LongAdder();
void increment() {
count.increment();
}
long get() {
return count.sum();
}
}
class ParallelSum {
static long sum(int[] numbers, int threads) throws Exception {
if (numbers.length == 0) return 0;
int workers = Math.min(threads, numbers.length);
int chunk = (numbers.length + workers - 1) / workers;
ExecutorService pool = Executors.newFixedThreadPool(workers);
try {
List<Callable<Long>> tasks = new ArrayList<>();
for (int start = 0; start < numbers.length; start += chunk) {
int from = start, to = Math.min(start + chunk, numbers.length);
tasks.add(() -> {
long s = 0;
for (int i = from; i < to; i++) s += numbers[i];
return s;
});
}
long total = 0;
for (Future<Long> f : pool.invokeAll(tasks)) total += f.get();
return total;
} finally {
pool.shutdown();
}
}
}
Common mistakes
volatile long count; count++— volatile gives visibility, not atomicity.- Sharing one accumulator across tasks.
- Never shutting down the executor (JVM won't exit).
Follow-up questions
- Implement with virtual threads (Java 21).
- When is
synchronizedpreferable to atomics?
More Async & Concurrency problems
- Retry with Exponential Backoff · Medium
- Implement debounce() · Medium
- Run Async Tasks with a Concurrency Limit · Medium