What is the difference between volatile and atomic reference in Java?

Alfred 164 Published: 07/24/2024

What is the difference between volatile and atomic reference in Java?

A fundamental question about concurrency in Java!

In Java, both volatile and atomic references are used to ensure thread-safety when shared variables are accessed concurrently by multiple threads. While they share a common goal, there is a crucial difference between the two.

Volatile

The volatile keyword is used to mark a variable as "volatile" in multi-threaded programming. When a variable is declared as volatile, Java ensures that any changes made to it will be immediately visible to all other threads that access it. This is achieved by inserting memory barriers, which are necessary for ensuring the visibility of shared variables across different threads.

In particular, when a thread modifies a volatile variable, the JVM will ensure that the change becomes visible to other threads before proceeding with any subsequent instructions. Conversely, when a thread reads a volatile variable, the JVM will ensure that it sees the most recent value written by any other thread before returning the result.

Atomic

Java provides several classes in the java.util.concurrent.atomic package for creating atomic variables. These classes include AtomicBoolean, AtomicInteger, AtomicLong, and more. When a variable is declared as an atomic variable, it ensures that updates to the variable are done atomically, meaning they are treated as a single, indivisible operation.

In other words, when multiple threads concurrently attempt to update an atomic variable, only one thread will successfully update the variable at a time, while others may observe the previous value. This is achieved by using low-level synchronization primitives, such as locks or compare-and-swap (CAS) operations.

Key differences

Now that we've covered the basics of both volatile and atomic, let's summarize the key differences:

Purpose: Volatile ensures visibility of shared variables, while Atomic ensures atomic updates to shared variables. Memory barriers: Volatile introduces memory barriers to ensure visibility, whereas Atomic uses low-level synchronization primitives for atomicity. Atomicity: Atomic ensures that updates are done atomically, meaning only one thread can update the variable at a time. Volatile, on the other hand, does not guarantee atomicity. Implementation: Volatile is implemented as a simple memory access, whereas Atomic uses more complex mechanisms like CAS operations or locks.

In summary, if you need to ensure that changes made to a shared variable are immediately visible to all threads, use volatile. If you need to ensure that updates to a shared variable are done atomically and safely, use an atomic reference.

Remember, it's essential to carefully choose the correct mechanism for your specific concurrency scenario, as misusing these constructs can lead to unexpected behavior or even deadlocks.

Java memory Model volatile

I'll respond in English this time!

The Java Memory Model (JMM) is a set of rules that govern how threads interact with shared variables in Java programs. In particular, the JMM provides a way to ensure that changes made by one thread to shared variables are visible to other threads.

One of the key concepts in the JMM is volatile. The volatile keyword is used to declare a variable as being accessed by multiple threads. When a variable is declared as volatile, it ensures that:

Writes are visible immediately: When a thread writes to a volatile variable, the change becomes visible to other threads immediately, without waiting for the write operation to be completed. Reads are up-to-date: When a thread reads a volatile variable, it sees the most recently written value.

To illustrate this, let's consider an example:

class Counter {

private volatile int count = 0;

public void increment() {

count++;

}

public int getCount() {

return count;

}

}

In this example, count is declared as a volatile variable. Suppose we have two threads, A and B, that access the Counter object simultaneously:

public class Main {

public static void main(String[] args) throws InterruptedException {

Counter counter = new Counter();

Thread A = new Thread(() -> {

for (int i = 0; i < 10000; i++) {

counter.increment();

}

});

Thread B = new Thread(() -> {

while (true) {

int value = counter.getCount();

System.out.println("Current count: " + value);

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

A.start();

B.start();

// Wait for the threads to finish

A.join();

B.join();

}

}

In this example, thread A increments the count variable 10,000 times in a loop. Thread B reads the current value of count repeatedly and prints it to the console.

Without the volatile keyword, there's a risk that thread B might read an outdated value of count, because updates made by thread A may not be visible immediately. However, with volatile, changes made by thread A are guaranteed to be visible to thread B instantly.

In summary, the Java Memory Model's volatile keyword ensures that writes are visible immediately and reads are up-to-date when accessing shared variables. This is particularly important in multi-threaded programs where multiple threads access the same data simultaneously.