Class 17: The volatile Keyword
Understand how Java helps threads see the latest value of a shared variable using the volatile keyword.
1. A Problem with Shared Variables
Imagine we have two threads working together.
- Thread 1 checks a variable repeatedly.
- Thread 2 changes the value of that variable.
We expect Thread 1 to see the updated value whenever Thread 2 changes it.
However, in multithreaded applications, threads can sometimes work with cached or previously read values.
Without proper coordination, one thread might not observe another thread's latest update when we expect it to.
Let's understand this with a simple example.
2. An Example Without volatile
class SharedData {
boolean running = true;
}
public class Main {
public static void main(String[] args)
throws InterruptedException {
SharedData data = new SharedData();
Thread worker = new Thread(() -> {
while (data.running) {
// Keep working while running is true
}
System.out.println("Worker stopped");
});
worker.start();
Thread.sleep(1000);
data.running = false;
System.out.println("Main thread changed running to false");
}
}
Let's understand what we expect to happen.
- The worker thread starts.
- It keeps checking whether
runningis true. - The main thread waits for one second.
- The main thread changes
runningto false. - We expect the worker thread to notice the change and stop.
But there is a problem: there is no proper visibility guarantee between these two threads for the shared variable.
The worker thread is not guaranteed to observe the updated value. The Java Memory Model also allows the compiler or runtime to optimize code in ways that make this kind of unsynchronized loop unsafe.
In fact, this example contains a data race. We should not rely on the worker eventually seeing the update.
3. Introducing volatile
Java provides the volatile keyword to help
solve this visibility problem.
We can declare the shared variable as volatile:
class SharedData {
volatile boolean running = true;
}
Now, when one thread updates this variable, other threads that read it are guaranteed to observe the update according to Java's memory visibility rules.
Let's use it in our complete example.
class SharedData {
volatile boolean running = true;
}
public class Main {
public static void main(String[] args)
throws InterruptedException {
SharedData data = new SharedData();
Thread worker = new Thread(() -> {
while (data.running) {
// Keep working while running is true
}
System.out.println("Worker stopped");
});
worker.start();
Thread.sleep(1000);
data.running = false;
System.out.println("Main thread changed running to false");
worker.join();
}
}
This time, the running variable is volatile.
When the main thread changes it to false, the worker thread can observe the updated value and exit the loop.
The join() call makes the main thread wait
until the worker thread finishes.
4. What Does volatile Actually Guarantee?
The most important thing to understand is visibility.
When a thread writes to a volatile variable, that write happens-before a subsequent read of that same variable by another thread.
In simpler words, volatile establishes a visibility guarantee between threads.
It also provides ordering guarantees for certain memory operations around volatile reads and writes.
For now, remember this simple idea:
volatile helps one thread see the latest value written to a shared variable by another thread.
5. Does volatile Make Operations Thread-Safe?
This is a very important question.
The answer is: not always.
Consider the following example:
class Counter {
volatile int count = 0;
void increment() {
count++;
}
}
We have declared count as volatile.
Does that mean multiple threads can safely increment this counter?
No.
The reason is that count++ is not one single
operation. It involves reading the value, increasing it,
and writing the result back.
Two threads can still interfere with each other's updates. This means a race condition is still possible.
volatile provides visibility, but it does not make
compound operations such as count++ atomic.
6. When Should We Use volatile?
volatile is useful when a shared variable is updated by one thread and read by other threads, and we need those threads to observe the updates.
A common example is a simple stop flag.
class Worker {
private volatile boolean running = true;
void stop() {
running = false;
}
void work() {
while (running) {
// Perform work
}
System.out.println("Work stopped");
}
}
Here, one thread can call stop(), while
another thread is executing work().
Because running is volatile, the worker
thread can observe the updated value.
This is a simple and useful situation where volatile can help.
7. volatile vs. synchronized
Both volatile and synchronized are useful in multithreaded programming, but they solve different problems.
| volatile | synchronized |
|---|---|
| Provides visibility and ordering guarantees for a variable. | Provides mutual exclusion and visibility through locking. |
| Does not acquire a monitor lock. | Acquires a monitor lock. |
Does not make compound operations such as count++ atomic. |
Can protect compound operations when all relevant access uses the same lock. |
| Useful for simple shared flags and state variables. | Useful when multiple operations need to be performed safely together. |
8. A Few Things to Remember
volatileis a Java keyword used with shared variables.- It provides visibility guarantees between threads.
- It helps other threads observe updates to a volatile variable.
- It also provides ordering guarantees defined by the Java Memory Model.
- It does not make compound operations such as
count++atomic. - Use synchronization or atomic variables when you need safe updates to shared counters.
Remember: volatile helps with visibility, but it does not automatically make every operation thread-safe.
Test Your Understanding
Let's see how well you understood this class!
Question 1: What is the main purpose of the volatile keyword?
Question 2: What does volatile guarantee when one thread writes to a volatile variable?
Question 3: Does volatile make count++ atomic?
Question 4: Which is a suitable use case for volatile?
Question 5: What is one important difference between volatile and synchronized?
Class Summary
Let's quickly revise what we learned in this class.
- Threads may need visibility of changes made to shared variables.
- The
volatilekeyword provides visibility guarantees. - A volatile write happens-before a subsequent read of the same variable.
- volatile does not make compound operations such as
count++atomic. - It is useful for simple shared flags and state variables.
- Synchronization and atomic variables are useful when we need safe updates to shared data.
Remember: volatile helps threads see updates, but it does not replace synchronization when multiple steps must be performed safely together.
You have completed 17 out of 46 classes.