volatile
multithreading
concurrency
java
synchronization

When to use volatile with multi threading?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

When working with multithreading in Java, understanding the use of the volatile keyword is crucial to ensure that threads can access and update shared variables safely. This article explores when to use volatile, how it works, and common use cases, along with potential limitations.

Understanding volatile

In Java, volatile is a keyword used to indicate that a variable's value will be modified by different threads. Declaring a variable as volatile ensures visibility and establishes that:

  • Changes to a volatile variable by one thread are immediately visible to other threads.
  • There is no caching of the variable, meaning that every read of the variable will fetch the latest value from the main memory.
  • It prevents instruction reordering concerning that variable.

Memory Visibility

One of the primary reasons to use volatile is to guarantee memory visibility. In Java's memory model, each thread has its own stack, where it can cache variables, leading to inconsistency when variables are updated in different threads. The volatile keyword prevents this, ensuring that all reads and writes go to the main memory directly.

Example of volatile

Consider a classic example of a flag that a thread uses to communicate with another thread.

java
1public class VolatileExample {
2    private volatile boolean flag = false;
3
4    public void writer() {
5        flag = true;
6    }
7
8    public void reader() {
9        if (flag) {
10            // Do something
11        }
12    }
13}

In this example, the flag variable is declared as volatile. When one thread updates the flag in the writer() method, the change is immediately visible to the thread executing the reader() method.

When to Use volatile

1. Simple Flags or States

Use volatile for simple flags, state indicators, or conditionals where a single variable is shared among multiple threads, and updates are predictable, with atomic operations sufficing.

2. No Compound Actions

volatile should be used when the variable's operations are atomic (like read or write) but not when dealing with compound actions. For instance, if variables need to be incremented or require compound actions, then Atomic classes or synchronized blocks are more appropriate.

3. Lazy Initialization

volatile ensures the latest value is seen for variables that might be lazily initialized, where the initialized value only needs to be written once and thereafter can be read. An example is the Double-Checked Locking pattern:

java
1public class Singleton {
2    private static volatile Singleton instance;
3
4    private Singleton() {
5    }
6
7    public static Singleton getInstance() {
8        if (instance == null) {
9            synchronized (Singleton.class) {
10                if (instance == null) {
11                    instance = new Singleton();
12                }
13            }
14        }
15        return instance;
16    }
17}

This pattern checks the singleton status twice, once without locking and once within a lock, ensuring thread-safe lazy initialization.

Limitations of volatile

  1. Not for Compound Actions: volatile cannot be used to ensure atomicity of compound operations, such as increments or checks followed by actions (e.g., if(flag) doSomething()). Consider using synchronized blocks or java.util.concurrent atomic classes.
  2. Order of Operations: volatile does not ensure the order in which threads execute, it only guarantees visibility. For strict sequencing, higher synchronization is required.
  3. Heavy Write Load: With numerous write operations, volatile can become a performance bottleneck because each operation bypasses caching.

Key Points Summary

UsageSuitable ContextsLimitations
Simple flags and state indicatorsSimple flags, read-mostly scenariosNot for atomic compound actions
Memory VisibilityVisibility between threadsNo guarantee of order or synchronization
Lazy InitializationSingleton, lazy fields needing only a write-once guaranteePerformance issues under heavy write loads

In conclusion, volatile is a powerful tool in Java threading for certain use cases, including simple flags and ensuring visibility between threads. However, understanding its limitations is key to applying it correctly without inadvertently introducing bugs or performance issues. When in doubt, consider complementing volatile with more robust constructs like synchronized blocks or higher-level concurrency abstractions.


Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.