Volatile Keyword in Java
The volatile keyword ensures that a variable's value may be modified by multiple threads simultaneously. The variable is always read from and written to the main memory rather than from thread-specific caches, ensuring visibility across threads.
This keyword guarantees that any change to the variable is immediately visible to all threads, preventing potential inconsistencies and data races. However, volatile does not provide atomicity or synchronization, so additional synchronization mechanisms should be used in conjunction with it when necessary.
Introduction to Volatile Keyword in Java
The volatile keyword in Java plays a pivotal role in concurrency, ensuring variable consistency across threads. Essentially, it guarantees visibility of changes across threads, making it indispensable for multi-threading.
Imagine two people coordinating to assemble a puzzle representing threads in Java. If one changes a piece (volatile variable), the volatile keyword ensures the other sees this change immediately. Misunderstanding the volatile keyword in Java can lead to confusing bugs and thread-safety issues.
The volatile keyword applies to both primitive types and objects. When declared volatile, a variable does not cache its value and always reads from the main memory. This is crucial, as it guarantees visibility and ordering, forbidding the compiler from reordering the code.
The volatile keyword cannot be used with methods or classes, it only applies to variables.
Volatile vs Synchronized
volatile | synchronized |
---|---|
Is a field modifier keyword that allows threads to read from the shared memory. | Is a method that allows only one thread to access a shared resource at a time. |
Is applicable to variables only | Is only applicable to block or methods |
It reads from the shared memory and not the cache memory. | It reads from the cache memory. |
Performance is relatively higher than synchronised functionality. | Performance is relatively lower than volatile due to ‘one thread at a time accessibility’ |
Solves the ‘visibility’ problem. | Solves the ‘synchronization’ problem. |
Any thread cannot be blocked for waiting. | Threads can be blocked for waiting. |
When to Use volatile Keyword?
Using the volatile keyword is important in scenarios where multiple threads are accessing shared variables. Without volatile, there can be issues with memory visibility and reordering.
In such cases, the values written by one thread may not be immediately visible to other threads, leading to incorrect or unexpected results.
By declaring a variable as volatile, we ensure that updates to that variable are immediately visible to other threads. It prevents the processor and compiler from reordering instructions involving the volatile variable, maintaining the intended program order.
Example
Let's imagine a scenario where we have a shared counter variable among multiple threads in a game. Each thread is responsible for updating the counter based on certain events. With proper synchronization, we may avoid issues with memory visibility and reordering.
Explanation:
In this example, each player thread increments the counter by 1 and prints its updated value. However, we need to use the volatile keyword to avoid inconsistencies in the counter.
Without volatile, the counter variable may be cached in the CPU registers, and each thread may have its own local copy. As a result, one player thread may update the counter, but other threads may not immediately see the updated value due to caching.
Moreover, processor and compiler optimizations may cause instruction reordering. This means that the increments performed by one thread could be reordered with other operations, leading to incorrect results.
Important Points
- Using the volatile keyword is limited to variables, and it is not allowed for classes or methods. When applied to a variable, volatile ensures its value is always read from the main memory instead of the local thread cache.
- When a variable is declared as volatile, the operations of reading and writing to it become atomic, meaning they are indivisible and can't be interrupted.
- The use of volatile reduces the risk of memory consistency errors, ensuring that changes made by one thread are visible to other threads.
- In Java, writing to a volatile variable establishes a "happen before" relationship, guaranteeing that subsequent reads of that variable will see the most up-to-date value.
- Volatile variables are always visible to other threads, ensuring other concurrent threads immediately see their changes.
- It's important to note that a volatile variable if it's an object reference, may have a null value.
- If a variable is not shared between multiple threads, there is no need to use the volatile keyword for that variable.
Example
Let's take the example discussed in the "When to use volatile" section.
Solution:
By declaring the counter variable as volatile, we ensure that updates to the counter are immediately visible to all threads. This prevents caching and reordering issues, allowing each player thread to consistently increment the counter.
Using volatile in this scenario guarantees that all player threads see the most up-to-date counter value and prevents any unexpected behavior due to memory visibility or reordering.
volatile in Java vs C/C++
In Java, the volatile keyword serves a different purpose compared to its usage in C/C++.
volatile | synchronized | |
---|---|---|
Purpose | Ensures visibility and consistency of variables. | Prevents compiler optimization of memory accesses to hardware devices. |
Caching | Variable value is not cached. | Prevents caching or optimization of memory accesses. |
Memory Model | Ensures variable is always read from main memory. | Ensures memory-mapped hardware accesses are not optimized away. |
Usage | Applied to variables in Java. | Applied to variables, usually for memory-mapped hardware in C/C++. |
Optimizations | Disables certain compiler optimizations. | Prevents removal or reordering of memory accesses. |
Conclusion
- Multithreading is a powerful operation that saves your time but you need to take responsibility for its implementation.
- Visibility problem can be easily fixed with the ‘Volatile’ keyword that will guarantee and keep all the local cache of the cores in your application updated.
- Threads cannot be kept blocked in waiting with volatile, but it is possible to keep threads in a waiting state when using ‘synchronized’.
- Understand volatile variables are not as fast as normal variables, and they cause reading and writing to be slower due to access to memory at each step. Cache memory accessibility is always faster than accessing RAM.
- You need to identify as to if volatile or synchronised is to be used, as both have specific use cases and their own limitations.
Hope this explanation makes you more aware about what’s happening in a multithreaded operation next time you implement it.