Java Volatile keyword

Primary Use Case of volatile:

Sameera De Silva
5 min readFeb 6, 2025

The volatile keyword is useful when variables are shared among multiple threads and their values are frequently updated by one thread and read by others.

Common use cases: flags to control thread execution, status indicators, simple variables used in patterns like double-checked locking.
Support for long and double:

volatile can be used with long and double variables to ensure automatic visibility between threads when their values are updated.
Synchronization Alternative:

volatile can be seen as an alternative to synchronization in specific scenarios like simple flags and status indicators but not for complex actions (e.g., compound actions like attempts++, where atomicity is needed).

Visibility of Updates:

All reader threads will see the updated value of a volatile variable immediately after a write operation.
Without volatile, different reader threads may see different values of the same variable due to thread-local caching.
Prevention of Code Reordering:

volatile informs the compiler that multiple threads will access a particular variable, and it prevents the compiler from performing reordering or optimizing the code in ways that could break the consistency of the variable’s value.
Effect on Compiler Optimization:

Without volatile, the compiler might reorder the code and use cached values of the volatile variable in a thread, rather than reading from main memory.
This could lead to inconsistent or outdated values being used, which volatile helps to prevent.

Summary of Key Points:

volatile is for shared variables that are updated by one thread and read by others.
It works with long and double variables to ensure visibility.
It does not guarantee atomicity, and therefore is not a replacement for synchronized in complex operations.
It ensures that reader threads see the updated value immediately after a write.
It prevents compiler optimizations and reordering that could break the behavior of multi-threaded programs.
This should be a good foundation for explaining how volatile works in Java! Let me know if you’d like to add anything else or clarify further.

When and when not to use?

The difference of volatile and synchronization

Alarm System with Attempt Counter (Using volatile)

⚡ Scenario Overview:

  • We’re simulating an Alarm System with:
  • A volatile boolean alarmActive to control the alarm's state (start/stop).
  • A volatile int attempts to count the number of times the alarm has sounded.
  • Two Threads:
  1. Alarm Thread: Continuously sounds the alarm, increasing the attempt count every minute.
  2. Monitoring Thread: Observes the attempt count and stops the alarm when attempts reach 3.

This demonstrates:

  • Thread coordination
  • Volatile usage with both boolean and int
  • A real-world, scalable concept

AlarmSystem.java

public class AlarmSystem {
private volatile boolean alarmActive = true; // Controls whether the alarm should keep sounding
private volatile int attempts = 0; // Counts the number of alarm attempts

// Synchronized method to ensure atomic increment
public synchronized void incrementAttempts() {
attempts++; // Safe increment, synchronized ensures no race conditions
}

// Method to start sounding the alarm
public void startAlarm() {
System.out.println("Alarm system activated!");

// Keep sounding the alarm until 'alarmActive' is set to false
while (alarmActive) {
System.out.println("🚨 Alarm is sounding! Attempt: " + (attempts + 1));

try {
// Simulate alarm sounding for 1 minute (for demo, we'll reduce to 3 seconds)
Thread.sleep(3000); // Replace with 60000 for real 1-minute delay
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Alarm interrupted unexpectedly!");
}

// After each "minute," increment the attempt count
incrementAttempts(); // Using synchronized method to increment safely
}

System.out.println("✅ Alarm has been deactivated.");
}

// Method to monitor the attempts and stop the alarm after 3 attempts
public void monitorAttempts() {
System.out.println("Monitoring alarm attempts...");

// Continuously check the attempts
while (alarmActive) {
if (attempts >= 3) { // If attempts reach 3, stop the alarm
System.out.println("❌ Maximum attempts reached. Stopping the alarm...");
alarmActive = false; // Send stop signal
}

try {
Thread.sleep(1000); // Check every second
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Monitoring interrupted unexpectedly!");
}
}
}
}

AlarmSystemApp.java

public class AlarmSystemApp {
public static void main(String[] args) {
AlarmSystem alarmSystem = new AlarmSystem();

// Thread 1: Responsible for sounding the alarm
Thread alarmThread = new Thread(new Runnable() {
@Override
public void run() {
alarmSystem.startAlarm();
}
});

// Thread 2: Responsible for monitoring the number of attempts
Thread monitorThread = new Thread(new Runnable() {
@Override
public void run() {
alarmSystem.monitorAttempts();
}
});

// Start both threads
alarmThread.start();
monitorThread.start();
}
}

Output

Alarm system activated!
🚨 Alarm is sounding! Attempt: 1
Monitoring alarm attempts...
🚨 Alarm is sounding! Attempt: 2
🚨 Alarm is sounding! Attempt: 3
❌ Maximum attempts reached. Stopping the alarm...
✅ Alarm has been deactivated.

Volatile for Visibility:

volatile is useful when we need to ensure visibility of a variable’s value across threads. It works well for simple reads and writes.
But synchronized is necessary when we have compound actions like attempts++, which involve reading, modifying, and writing a value, and we need to ensure these operations happen atomically to prevent race conditions.

Why synchronized is Needed Here:

Atomicity: The increment operation (attempts++) needs to be atomic. That means we want the read, increment, and write steps to happen as one uninterrupted operation.
By using synchronized, we ensure that only one thread can perform the increment at a time.

Thread Safety: By synchronizing the method, we avoid scenarios where multiple threads try to modify attempts simultaneously, leading to incorrect results.

The concepts of Simple Action (Read + Write):

You read a value from a variable.
You write a new value to a variable.
So all together 2 steps

A simple read or simple write refers to an operation where:

private volatile boolean flag;  // Simple read/write

public void toggleFlag() {
flag = !flag; // Simple write
}

public boolean getFlag() {
return flag; // Simple read
}

These operations are atomic by themselves, meaning they don’t involve multiple steps that could be interrupted by other threads.
In this case, volatile works well because it ensures visibility — meaning that any changes made by one thread to a volatile variable are
immediately visible to other threads, and no caching or optimization is done on that variable by the CPU.

Compound Actions:

3 Steps (Read + Modify + Write)
A compound action is an operation that involves multiple steps, typically something that:

Reads a variable.
Modifies the value of the variable.
Writes the new value back.
These actions are not atomic because the operation involves multiple steps that can be interrupted by another thread. For example, consider the operation attempts++:

Read: The thread reads the current value of attempts.
Modify: The thread increments the value of attempts (adds 1).
Write: The thread writes the new value back to attempts.
Since other threads could also be reading and modifying attempts at the same time, a race condition can occur.
For example, two threads could both read the same value of attempts, increment it, and write it back, resulting in the value being incremented incorrectly.

Key Difference:

Simple Actions: Just a read and a write to a variable (e.g., flag = !flag). These are safe when using volatile because there’s no intermediate modification or calculation.

Compound Actions: Involve reading, modifying, and writing (e.g., attempts++). These require synchronization to ensure atomicity because the intermediate modification step can lead to race conditions when multiple threads are involved.

--

--

No responses yet