DEV Community

Cover image for How to Boil Potatoes? I Thought I Knew — Until I Didn't.
Mehuli Mukherjee
Mehuli Mukherjee

Posted on

How to Boil Potatoes? I Thought I Knew — Until I Didn't.

From Potatoes to Threads: A Real-World Journey Into Java Concurrency Optimization

I thought I knew how to boil potatoes.

You put them in a pot, add water, wait for it to boil, and eventually they soften up. Simple, right?

But recently, I visited a friend to help brainstorm his startup idea. While chatting in the kitchen, he tossed a couple of potatoes into a bowl with a splash of water, popped it in the microwave, and seven minutes later, they were done. No mess. No babysitting the stove. Just... done.

I was amazed.

It wasn’t just about potatoes. It was a perfect metaphor for something I face every day in software development: Optimization.

Old Habits Die Hard

In code, we often stick to what works. We rely on familiar patterns and tools. They may not be the fastest, cleanest, or safest, but hey, they get the job done. Just like the stovetop potato.

But what if there's a better way — one that saves time, reduces complexity, and delivers the same result?

That’s what optimization is all about.

A Real-World Concurrency Mess

Not long after my potato revelation, I faced a hairy Java concurrency problem at work.

We had a multi-threaded task scheduler in our enterprise application that processed transactional data across shared resources. Under pressure, the system began to crack: deadlocks, inconsistent states, unpredictable behaviors.

The original solution was layered with:

  • Deeply nested ReentrantLocks
  • Shared mutable maps guarded with synchronized
  • Ad-hoc retry loops and timeouts

It worked on paper. But it was complex, fragile, and hard to reason about. Performance was tanking. Debugging it felt like boiling a potato with a candle.

The Microwave Approach: Think Simpler

I paused and asked myself: Are we overcomplicating this?

Here’s what we did instead:

  • ExecutorService (fixed or scalable thread pool)
  • ConcurrentHashMap < String, AtomicReference < ResourceState > > — immutable transactional state updates
  • Lock-free atomic updates using compareAndSet
  • CompletableFuture for chaining + timeout/failure handling
  • Backoff with retry scheduler (ScheduledExecutorService)

Here's a simplified example:

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.UnaryOperator;

class TransactionProcessor {

    // Represents an immutable transactional state for a resource
    static class ResourceState {
        final int transactionCount;
        final long lastUpdated;

        ResourceState(int transactionCount, long lastUpdated) {
            this.transactionCount = transactionCount;
            this.lastUpdated = lastUpdated;
        }

        ResourceState applyTransaction() {
            return new ResourceState(transactionCount + 1, System.currentTimeMillis());
        }

        @Override
        public String toString() {
            return "count=" + transactionCount + ", time=" + lastUpdated;
        }
    }

    private final ConcurrentHashMap<String, AtomicReference<ResourceState>> resourceMap = new ConcurrentHashMap<>();
    private final ExecutorService executor = Executors.newFixedThreadPool(8);
    private final ScheduledExecutorService retryScheduler = Executors.newScheduledThreadPool(2);

    private static final int MAX_RETRIES = 3;
    private static final int BACKOFF_MS = 200;

    public void processTransaction(String resourceKey) {
        submitWithRetry(resourceKey, 0);
    }

    private void submitWithRetry(String resourceKey, int attempt) {
        executor.submit(() -> {
            try {
                AtomicReference<ResourceState> ref = resourceMap.computeIfAbsent(
                        resourceKey, k -> new AtomicReference<>(new ResourceState(0, System.currentTimeMillis()))
                );

                boolean updated = false;
                for (int i = 0; i < 5; i++) { // CAS retry loop
                    ResourceState current = ref.get();
                    ResourceState updatedState = current.applyTransaction();
                    if (ref.compareAndSet(current, updatedState)) {
                        System.out.printf("Updated: %s (attempt %d)%n", resourceKey, updatedState, attempt);
                        updated = true;
                        break;
                    }
                }

                if (!updated) throw new RuntimeException("CAS failed after multiple retries");

            } catch (Exception e) {
                if (attempt < MAX_RETRIES) {
                    int delay = BACKOFF_MS * (attempt + 1);
                    System.out.printf("Retry attempt %d after %dms due to: %s%n",
                            resourceKey, attempt + 1, delay, e.getMessage());
                    retryScheduler.schedule(() -> submitWithRetry(resourceKey, attempt + 1),
                            delay, TimeUnit.MILLISECONDS);
                } else {
                    System.err.printf("Failed after %d attempts: %s%n",
                            resourceKey, attempt + 1, e.getMessage());
                }
            }
        });
    }

    public void shutdown() throws InterruptedException {
        executor.shutdown();
        retryScheduler.shutdown();
        executor.awaitTermination(5, TimeUnit.SECONDS);
        retryScheduler.awaitTermination(5, TimeUnit.SECONDS);
        System.out.println("Shutdown complete.");
    }

    public void printFinalStates() {
        System.out.println("\n=== Final Resource States ===");
        resourceMap.forEach((key, ref) -> System.out.println(key + " => " + ref.get()));
    }

    public static void main(String[] args) throws InterruptedException {
        TransactionProcessor processor = new TransactionProcessor();
        for (int i = 0; i < 50; i++) {
            String resourceKey = "resource-" + (i % 5); // 5 shared resources
            processor.processTransaction(resourceKey);
        }

        Thread.sleep(3000); // allow processing time
        processor.printFinalStates();
        processor.shutdown();
    }
}

Enter fullscreen mode Exit fullscreen mode

It wasn’t fancy, but it was faster, safer, and a whole lot easier to maintain.

What Makes This Optimized?

Image description

Can Scale To:

  • Millions of resources using sharded maps or local caches.
  • Async I/O or DB commits with CompletableFuture chaining.
  • Resilience features like circuit breakers or observability tools (Prometheus, Micrometer).

The Real Lesson

Optimization isn’t always about clever hacks or tuning JVM settings. It’s often about rethinking the whole process.

Ask yourself:

  • Am I doing this the hard way just because it’s what I know?
  • Is there a tool, pattern, or paradigm that simplifies this?

Sometimes the solution is already in the kitchen. You just need someone to show you the microwave.

Final Thoughts

Whether it's potatoes or production code, the goal is the same: get the job done well, with as little pain as possible.

Next time you’re stuck in complex, boilerplate-heavy code, step back and ask:

Am I using a stove when I could be using a microwave?

Top comments (4)

Collapse
 
deividas_strole profile image
Deividas Strole

Great and inspiring article! As with many things in life, sometimes you need to step back and think outside the box… or at least look at the boiling potatoes! 😄 Thanks for sharing!

Collapse
 
mehulimukherjee profile image
Mehuli Mukherjee

Thank you @deividas_strole , I am glad you liked it :)

Collapse
 
mtsammy40 profile image
Samuel Mutemi

Randomly stumbled upon this but you might have just saved my potatoes. Thanks🙏.

Collapse
 
mehulimukherjee profile image
Mehuli Mukherjee

Hey, Thank you very much @mtsammy40 :)