Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this a dozen times in code reviews: a developer is building a multi-threaded counter, they realize they're having race conditions, and their "fix" is to simply slap the volatile keyword on the integer. They assume that because volatile ensures every thread sees the most recent value, the operation is now thread-safe. It's a logical leap, but it's a dangerous one.

The Volatile Trap: Thinking Visibility Equals Atomicity

Here is the core of the problem. Let's look at a simple request counter. You might think this is safe because of volatile:

public class RequestCounter {
    private volatile int count = 0;

    public void increment() {
        count++; // This is where the disaster happens
    }

    public int getCount() {
        return count;
    }
}

At first glance, this looks fine. But count++ isn't one operation; it's three: read the value, add one to it, and write it back. If two threads read the value 10 at the exact same time, they both calculate 11, and they both write 11 back. You just lost an update. volatile ensures that the threads see the latest value, but it does absolutely nothing to stop them from stepping on each other's toes during that read-modify-write cycle.

Leveraging AtomicInteger for Lock-Free Updates

To fix this without the heavy overhead of synchronized blocks—which essentially put your threads in a waiting line—Java gives us the java.util.concurrent.atomic package. The most common tool here is AtomicInteger.

When you use AtomicInteger, you aren't just wrapping an int; you're using a set of methods designed to be "atomic," meaning they happen as a single, indivisible unit of work. Here is how I would actually write that counter:

import java.util.concurrent.atomic.AtomicInteger;

public class RequestCounter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // This is the atomic equivalent of count++
    }

    public int getCount() {
        return count.get();
    }
}

Now, no matter how many threads hit increment(), you will never lose a count. You don't have to worry about manual locking or memory barriers because the heavy lifting is handled at the hardware level.

The Secret Sauce: Compare-And-Swap (CAS)

You might be wondering how Java does this without using synchronized. It uses a mechanism called Compare-And-Swap (CAS). Instead of locking the variable, the CPU does something like this:

  • "I think the current value is 10."
  • "I want to change it to 11."
  • "If the value is still 10 when I actually go to write it, go ahead and change it. If it's not 10 anymore, someone beat me to it—fail the operation and let me try again."

This is called "optimistic locking." It assumes that most of the time, there won't be a collision. If there is, it just loops and tries again. This is significantly faster than putting a thread to sleep and waking it up, which is what happens when you use traditional locks.

Beyond AtomicInteger, you'll find AtomicLong for larger numbers, AtomicBoolean for flags, and AtomicReference when you need to atomically update an entire object. Use them whenever you have a single shared variable that needs to be updated across threads, and you'll save yourself from a world of debugging pain.




📋 Practical Task

Implementing a Thread-Safe Global Request Tracker

You are building a monitoring system for a high-traffic web server. You need to create a class called GlobalRequestTracker that tracks two things across multiple concurrent threads: the total number of requests received (which could be very large) and the current number of active requests (which fluctuates as requests start and finish).

Requirements:

  • Use AtomicLong for the total request count to prevent overflow.
  • Use AtomicInteger for the active request count.
  • Implement a method recordRequestStart() that increments both the total and the active counts.
  • Implement a method recordRequestEnd() that decrements only the active count.
  • Implement a method getStats() that returns a string summarizing the current totals.

Ensure that your implementation is entirely lock-free by avoiding the synchronized keyword.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.