Coding problem · Medium · Async & Concurrency

Thread-Safe Counting with ExecutorService

Medium Language practicals Seen at Amazon, Uber, LinkedIn, Goldman Sachs

Implement two classes:

  1. HitCounter — increment() and get() that stay correct when called from many threads at once (no lost updates).
  2. ParallelSum.sum(int[] numbers, int threads) — split the array into threads chunks, sum each chunk on an ExecutorService, and return the total as a long. 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 synchronized preferable to atomics?