The volatile
keyword in Java is used to indicate that a variable’s value will be modified by multiple threads. It ensures visibility and ordering of changes to the variable across threads, preventing issues like stale data and thread interference.
1. Key Features of volatile
- Visibility: Changes made by one thread to a
volatile
variable are immediately visible to other threads. - Ordering: Ensures that reads and writes to the variable are not reordered by the compiler or CPU.
- No Atomicity: The
volatile
keyword does not provide atomicity for compound operations (e.g., incrementing a variable).
2. When to Use volatile
- When a variable is shared among multiple threads, and its value is updated by one thread and read by others.
- When the variable’s value does not depend on its current state (e.g., flags, status indicators).
- When atomicity is not required (use
AtomicInteger
,AtomicLong
, etc., for atomic operations).
3. Example: Using volatile
for Visibility
Without volatile
, changes made by one thread may not be visible to other threads due to CPU caching.
class SharedResource {
private volatile boolean flag = false;
public void setFlag() {
flag = true; // Write operation
}
public void doWork() {
while (!flag) { // Read operation
// Busy-wait until flag becomes true
}
System.out.println("Flag is now true.");
}
}
public class VolatileExample {
public static void main(String[] args) throws InterruptedException {
SharedResource resource = new SharedResource();
Thread writerThread = new Thread(() -> {
try {
Thread.sleep(1000); // Simulate some work
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.setFlag(); // Set flag to true
});
Thread readerThread = new Thread(resource::doWork);
writerThread.start();
readerThread.start();
writerThread.join();
readerThread.join();
}
}
In this example:
- The
flag
variable is marked asvolatile
, ensuring that changes made by thewriterThread
are immediately visible to thereaderThread
. - Without
volatile
, thereaderThread
might never see the updated value offlag
.
4. volatile
vs synchronized
Feature | volatile | synchronized |
---|---|---|
Visibility | Ensures visibility of changes. | Ensures visibility and atomicity. |
Atomicity | Does not provide atomicity. | Provides atomicity. |
Performance | Lightweight. | Heavier due to locking. |
Use Case | Single variable updates. | Compound operations or critical sections. |
5. Limitations of volatile
- No Atomicity: The
volatile
keyword does not ensure atomicity for compound operations likei++
. - Not Suitable for All Scenarios: For complex synchronization, use
synchronized
blocks or higher-level concurrency utilities likeReentrantLock
orAtomic
classes.
6. Example: volatile
with Atomic Operations
For atomic operations, use classes from the java.util.concurrent.atomic
package (e.g., AtomicInteger
).
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Atomic operation
}
public int getCount() {
return count.get();
}
}
public class AtomicExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Count: " + counter.getCount()); // Output: 2000
}
}
7. Best Practices
- Use
volatile
for simple flags or status variables shared across threads. - For compound operations, use
synchronized
or atomic classes. - Avoid using
volatile
for complex synchronization scenarios. - Always test multi-threaded code thoroughly to ensure correctness.
By understanding the volatile
keyword, you can write thread-safe code that ensures visibility and ordering of shared variables in multi-threaded environments!