The volatile keyword plays a relatively important role in Java multi-threading. The main function of volatile is to keep variables visible in multi-threading and is the lightest synchronization mechanism provided in Java.
Visibility
All variables in Java's memory model (the variables here are global variables, not local variables. There is no thread-safe problem in the method, because variables are destroyed as the method is called) are stored in the main memory. Each thread has its own working memory. Each time the thread executes, a copy of the variable will be obtained from the main memory, and the operation of the variable will be performed in the thread's working memory. Different threads cannot share working memory, and can only read the copy of the variable from the main memory. Specifically, it can be expressed in the figure below:
However, for volatile (using synchronized/final modifications, the above rule is broken, that is, when the thread modifies the value of the variable, other threads can immediately know the change of the variable. However, for ordinary variables, when a thread modifies a variable, it is necessary to write the variable back to the main memory first, and other threads will only be visible to the thread after reading the variable from the main memory. It seems that from the above description, it can be deduced that as long as a variable modified by volatile is used, it is guaranteed that the variable is safe to operate in a multi-threaded environment, because it is visible to the working memory of all threads, that is, consistent. This is true, but many operations in Java are not atomic, so using volatile in some Java operations cannot guarantee thread safety issues. Let's look at an example:
public class test{private static volatile t=0; private static int add(){ return t++; } public static void testVolatile(){ for (int i=0;i<20;i++){ Thread thread=new Thread(()-> { for (int j=0;j<1000;j++) { add(); } }); thread.start(); } while (Thread.activeCount()>1){ Thread.yield(); } System.out.println(t); } public static void main(String[] args){ testVolatile(); }}It is expected that the t value should be 20,000, but there will be a situation where the t value is less than 20,000. You should guess the reason. The problem lies in t++. T++ is not an atomic operation. In java, the t++ operation means first to obtain the t value, then add 1, and then assign t. When obtaining the t value, it is modified by volatile, so the latest value of the thread can be obtained. However, when adding 1, it cannot be guaranteed. It is possible that other threads have already added 1.
So which scenario is the most suitable to use volatile?
* In variable operation does not depend on the current value
* Variables do not need to participate in invariant constraints with other state variables
Translated into Chinese means that for variables that have both read and write in multi-threads, you can use volatile to modify them. In this way, you should not use lock/synchronized operations for read operations. Reading directly is because variables are visible.