Java Volatile Keyword
The compiler, runtime, or processors may use any kind of optimization if there aren't any required synchronizations. Although most of the time these improvements are advantageous, they occasionally can result in minor problems.
Among the optimizations that could surprise us in concurrent situations are caching and reordering. The volatile keyword is one of the various mechanisms offered by Java and the JVM for managing memory order. Program instructions must be carried out by processors. As a result, they must pull the necessary data and program instructions from RAM.
Fetching from RAM isn't the best option for CPUs because of how many instructions per second they can process. Processors are employing techniques like Out of Order Execution, Branch Prediction, Speculative Execution, and, of course, Caching to improve this problem. When a Java variable is "being stored in main memory," the Java volatile keyword is used to indicate this.
Since Java 5, the volatile keyword ensures more than only main memory access for volatile variables. In the sections that follow, I shall explain.
In this article, we'll concentrate on the volatile keyword, a fundamental yet sometimes misunderstood Java language notion. We'll begin by providing some background information on the fundamental principles of computer architecture before becoming acquainted with Java's concept of memory order.
Multiple threads can change a variable's value by using the volatile keyword. Making classes thread safe is another use for it. It means that using a method or an instance of a class by several threads is not problematic. Both primitive types and objects are compatible with the volatile keyword.
The volatile keyword always reads the variable's value from the main memory rather than caching it. Classes and methods cannot be combined with the volatile keyword. It is utilized with variables, though. It also ensures order and visibility. It stops code from being rearranged by the compiler.
The volatile keyword is required to prevent such accesses from being optimized away by the compiler since the contents of the specific device register could change at any time.
Using volatile is yet another method of making a class thread-safe (along with synchronized and atomic wrapper). A method or class instance is said to be thread-safe if it can be utilized by several threads concurrently without encountering any issues.
Example
By consider the example given below. Variable changes between threads are guaranteed to be visible thanks to the Java volatile keyword. Let me explain because this might sound a little abstract.
class S_Obj
{
// Changes made to s_Var in one thread
// may cannot reflect immediately in other thread
static int s_Var = 9;
Let's say that SharedObj is being worked on by two threads. Each thread may have its own local copy of the sharedVariable if they are running on different CPUs. If a value is modified by one thread, the original value stored in main memory could not immediately reflect the change. This is dependent on the cache's write policy. Since the other thread is currently unaware of the modified value, inconsistent data results.
Simple example of volatile
class Testcase
{
static int v=8;}
Assume that two threads are working on the same class in the example. Each thread has its own local copy of var, and both threads are running on different CPUs. If any thread updates it, the modified value won't be reflected in the main memory's original. Because the other thread is unaware of the updated value, it causes data inconsistency.
class Test
{
static volatile int v =8;
}
Static variables are class members that are shared by all objects in the example above. In the main memory, there is just one copy. A volatile variable's value will never be kept in the cache. The main memory will be used for all read and write operations.
When to Use the Volatile
- If you wish to automatically read and write long and double variables, you can do so by using a volatile variable.
- It can be utilized as a different approach to synchronization in Java.
- After the write operation is finished, the volatile variable's changed value will be visible in all reader threads. If the volatile keyword is not used, multiple reader threads can view different values.
- It is employed to let the compiler know that several threads will make use of a specific statement. It prohibits the compiler from performing any optimization or reordering.
- If you don't utilize volatile variables, the compiler can rearrange the code and read from main memory rather than writing the volatile variable value to cache.
A volatile variable's reading or writing does not prevent other threads from doing the same. Use the synchronized keyword around important portions to make this happen.
You may alternatively use one of the various atomic data types present in the java. util. concurrent package as an alternative to a synchronized block. The AtomicLong, AtomicReference, or one of the others, for instance. This is not ensured if the variable is not made volatile.
Important Points of Volatile
- The keyword volatile is compatible with variables. It is forbidden to use volatile keywords with classes and methods.
- It ensures that the volatile variable's value will never be read from the local thread cache but instead from the main memory.
- Reads and Writes are atomic for volatile variables that you have declared.
- The likelihood of a memory consistency error is decreased.
- In Java, any write to a volatile variable creates an occur before relationship with subsequent readings of the same variable.
- Other threads can always see the volatile variables.
- An object reference's volatile variable could be null.
- You do not need to use the volatile keyword with a variable if it is not shared by more than one thread.
Volatile vs Synchronized
Although it is not a synchronized keyword's replacement, a volatile keyword may be utilized in some circumstances.
Java's synchronized keyword guarantees mutual exclusion and visibility. If we synchronies the blocks of threads that change the value of the shared variable, then only one thread can enter the block and changes will be reflected in main memory. If any other thread tries to enter the block concurrently, they will all be blocked and put to sleep.
In some situations, visibility may be the only thing we want, not atomicity. In this case, using synchronized is excessive and could lead to scalability issues. Volatile saves the day in this situation. The visibility aspects of synchronized variables apply to volatile variables, but not the atomicity features. All updates to and reads from the main memory will be made in order to avoid caching the volatile variable's values. However, because atomicity is preferred much of the time, the use of volatile is constrained to a very small number of situations.
For instance, a seemingly simple increment statement like x = x + 1 or x++ is actually a compound read-modify-write series of operations that must carry out atomically.
Examples of Volatile Keyword
We have defined a class in the example below that raises the counter value. When the thread starts running, the modified value and the old value are passed to the run () method in the VolatileThread.java file. The array of threads is defined in the main class.
Example 1:
public class Vol_Data
{
private vol int count = 0;
public int getCount ()
{
return count;
}
public void increCount ()
{
++count; // the value of counter increses by 1
}
}
Example 2:
public class VolThread extends Thread
{
private final VolData da;
public VolThread (VolData da)
{
this. da = da;
}
@Override
public void run ()
{
int oldV = da. getCount ();
System.out.println("[Thread " + Thread.currentThread(). getId () + "]: Old v = " + oldV);
da. increCount ();
int newV = da. getCount ();
System.out.println("[Thread " + Thread.currentThread(). getId() + "]: New v = " + newV);
}
}
Example 3:
public class VolatileMain
{
private final static int noOfThreads = 2;
public static void main (String [] args) throws InterruptedException
{
VolDa volDa = new VolDa ();
Thread [] threads = new Thread[noOfThreads]; // Thread array is created
for (int i = 0; i < noOfThreads; ++i)
threads[i] = new VolatileThread(volDa);
for (int i = 0; i < noOfThreads; ++i)
threads[i]. start (); //starts all reader threads
for (int i = 0; i < noOfThreads; ++i)
threads[i]. join (); //wait for all threads
}
}
Output:
[Thread 9]: Old v = 0
[Thread 9]: New v = 1
[Thread 10]: Old v= 1
[Thread 10]: New v = 2
Volatile variables are read and written into main memory when they are read and written, respectively. Accessing the CPU cache is less expensive than reading or writing to main memory. Accessing volatile variables also prevents the common performance improvement method of instruction reordering. Therefore, you should only use volatile variables when it is absolutely necessary to ensure variable visibility.