Case explanation of java volatile

  • 2021-10-15 10:40:35
  • OfStack

This article is a summary of volatile from java concurrent programming practice.

To say volatile, you must first understand memory visibility. Let's start with memory visibility.

1. Memory visibility

Visibility is a complex attribute, because errors in visibility always go against our intuition. In a single-threaded environment, you always get the same value if you write a value to a variable and then read the variable without other writes. It seems natural. However, this is not the case when the read and write operations are performed in different threads, which may sound unacceptable. Often, it is impossible to ensure that the thread performing the read operation will see the value written by other threads in due course, and sometimes it is even impossible at all. In order to ensure the visibility of memory write operations between multiple imaginations, synchronization mechanism must be used. For the following code:


public class NoVisibility {
    private static boolean ready;
    private static int number;
    
    private static class ReaderThread extends Thread{
        public void run(){
            while(!ready)
                Thread.yield();
            System.out.println(number);
        }
    }
    
    public static void main(String[] args){
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

NoVisibility may continue to loop because the reader thread may never see the value of ready. A more bizarre phenomenon is that Novisibility may output 0, because the reader thread may see the value written to ready, but not the value written to number later. This phenomenon is called "reordering (Reordering)". As long as the reordering condition cannot be detected in one thread (even if the reordering in that thread can be clearly seen in other threads), there is no guarantee that the operations in the thread will be performed in the order specified in the program. When the main thread writes to number first and then writes to ready without synchronization, the order seen by the reader thread may be completely opposite to the order written.

In the absence of synchronization, the compiler, processor, runtime, and so on can make some unexpected adjustments to the order in which operations are executed. In a multithreaded program that lacks sufficient synchronization, it is almost impossible to draw a correct conclusion by judging the execution order of memory operations.

This may seem like a failed design, but it enables JVM to make full use of the powerful performance of modern multi-core processors. For example, in the absence of synchronization, the java memory model allows the compiler to reorder operations and cache values in registers. In addition, it allows CPU to reorder operations and loop values into processor-specific caches.

2. Volatile variable

The java language provides a slightly weaker synchronization mechanism, volatile variables, to ensure that other threads are notified of variable updates. When a variable is declared as volatile, both the compiler and the runtime notice that the variable is shared, so operations on the variable and other memory operations are not reordered. volatile variables are not cached in registers or invisible to other processors, so reading volatile-type variables always returns the most recently written value.

The difference between volatile and locking mechanism:

The locking mechanism ensures both visibility and atomicity, whereas the volatile variable only ensures visibility.

The volatile variable should be used if and only if all of the following conditions are met:

Writing to a variable does not depend on the current value of the variable, or you can ensure that only a single thread updates the value of the variable. This variable will not be included in the invariance condition together with other state variables 1. There is no need to lock when accessing variables.

Related articles: