Java Concurrency - Volatile

Volatile variables usage

Volatile Variables

Volatile variables in Java are used to ensure visibility of changes to variables across multiple threads. When a variable is declared as volatile, it guarantees that any thread reading the variable will see the most recent write to the variable. This ensures that changes made by one thread are visible to other threads immediately.

Most of the time, volatile is used to ensure visibility of changes to a variable across threads, rather than atomicity of operations. Common use cases are boolean flags that are shared between threads, such as signaling the termination of a thread or a loop.

Use Cases

Volatile variables are suitable for scenarios where one thread writes to a variable, and multiple threads read from it. They provide a lightweight synchronization mechanism to ensure visibility of changes without the overhead of locks or synchronized blocks.

When to use volatile variables:

Flag variables: Volatile variables are commonly used for boolean flags that are shared between threads. For example, a flag to signal the termination of a thread or a loop.

Simple state variables: When a single thread writes to a variable, and multiple threads read from it, volatile variables can ensure that changes are visible across threads.

Non-blocking algorithms: Volatile variables are essential in building non-blocking algorithms, where threads don’t have to wait for locks, preventing deadlocks.

High contention scenarios: In scenarios where contention is high, volatile variables can be more efficient than locks because they avoid the overhead of context switching.

Read-mostly scenarios: In scenarios where a variable is mostly read and infrequently updated, volatile variables can provide better performance than locks.

When not to use volatile variables:

Complex state management: If the state management involves complex operations or multiple variables that need to be updated atomically, volatile variables are insufficient. Use locks or synchronized blocks instead.

Mutual exclusion: Volatile variables do not provide mutual exclusion, so they are not suitable for scenarios where multiple threads need to update a shared variable atomically.

Performance is critical: If performance is critical and contention is low, using volatile variables might introduce unnecessary overhead compared to other synchronization mechanisms.

When atomicity is required: Volatile variables do not provide atomicity for compound operations. If atomicity is required, consider using atomic variables or locks.

When ordering is important: Volatile variables ensure visibility of changes but do not guarantee ordering of operations. If ordering is important, use other synchronization mechanisms.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class VolatileExample {
private static volatile boolean running = true;

public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(new Runnable() {
@Override
public void run() {
while (running) {
System.out.println("Worker thread is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Worker thread has stopped.");
}
});

worker.start();

// Let the worker thread run for 5 seconds
Thread.sleep(5000);

// Stop the worker thread
running = false;

// Wait for the worker thread to finish
worker.join();
System.out.println("Main thread has finished.");
}
}

Conclusion

Volatile lacks the atomicity guarantees of synchronized blocks or locks. It only ensures visibility of changes across threads. Volatile variables are suitable for simple flag variables and scenarios where one thread writes to a variable, and multiple threads read from it. However, for more complex state management or atomic operations, consider using locks or atomic variables.