Volatile and Atomic

Volatile #

The volatile keyword in Java is used to ensure visibility and ordering of variables across threads. When a variable is declared as volatile, it guarantees that reads and writes to that variable are directly made to the main memory, and not cached by individual threads.

Key Characteristics: #

  • Visibility: Ensures that changes to a variable are immediately visible to other threads. If one thread updates a volatile variable, other threads will see the updated value immediately.
  • Atomicity: While volatile guarantees visibility, it does not guarantee atomicity. Operations on volatile variables are atomic only if the operations themselves are atomic. For example, reading or writing an int or boolean variable is atomic, but operations like incrementing (i++) are not atomic.
  • Ordering: The volatile keyword prevents reordering of reads and writes to the variable. This means that any read of a volatile variable will see the latest write, and no reordering will happen around the volatile read or write.

Example:

public class VolatileExample {
    private volatile boolean flag = false;

    public void writer() {
        flag = true; // Write to the volatile variable
    }

    public void reader() {
        if (flag) {
            System.out.println("Flag is true");
        }
    }
}

In the example, the flag variable is declared as volatile, ensuring that any change made to flag in the writer() method is immediately visible to the reader() method.

Atomic Operations #

Atomic operations are operations that are performed as a single, indivisible step. In Java, atomic operations are provided by the java.util.concurrent.atomic package, which includes classes like AtomicInteger, AtomicLong, AtomicBoolean, and AtomicReference.

Key Characteristics:

  • Atomicity: Guarantees that the operations on the variables are atomic, meaning they cannot be interrupted. For example, AtomicInteger provides methods like incrementAndGet() which atomically increments the value and returns the updated value.
  • Lock-Free: Many atomic classes use low-level hardware operations and avoid the use of traditional locking mechanisms, thus providing better performance in some scenarios.
  • Volatility: Atomic variables are implicitly volatile. They ensure visibility of changes across threads and guarantee that the latest value is always visible.

Example:

import java.util.concurrent.atomic.AtomicInteger;

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

    public void increment() {
        count.incrementAndGet(); // Atomically increments the value
    }

    public int getCount() {
        return count.get(); // Gets the current value atomically
    }
}

In this example, AtomicInteger is used to maintain a counter that can be safely updated by multiple threads. The incrementAndGet() method atomically increments the counter and returns the new value.

Comparison #

Volatile:

  • Guarantees visibility of changes to variables.
  • Does not guarantee atomicity for complex operations.
  • Useful for flags and simple variables where atomic operations are not needed.

Atomic Operations:

  • Guarantees both atomicity and visibility.
  • Useful for counters, accumulators, and other scenarios requiring atomic updates.
  • Provides higher-level constructs for common atomic operations (e.g., incrementing).

Conclusion #

Use the volatile keyword when you need to ensure that a variable’s value is visible across threads, and you don’t need to perform complex operations on that variable. Use atomic classes from the java.util.concurrent.atomic package when you need to perform thread-safe operations on variables that involve more than just reading and writing, such as increments, updates, or compound actions.