volatile keyword (modified variable)

1. Meaning

It is a lighter-weight synchronization mechanism than the sychronized keyword. When accessing volatile variables, no locking operations are performed.

2. Function

Volatile is a type specifier. The role of volatile is to be used as an instruction keyword to ensure that this instruction will not be omitted due to compiler optimization, and requires direct reading every time.

  1. Ensure visibility
  2. Disable instruction reordering optimization

Instruction reordering optimization: ordinary variables only ensure that the correct results can be obtained in all places that rely on the assignment results during the execution of the method, but cannot guarantee that the order of variable assignment operations is consistent with the execution order in the program code

3. How to ensure visibility

The new value can be immediately synchronized to the main memory and refreshed from the main memory immediately before each use.

  • The variable modified by volatile is the value of the main memory that is directly taken, that is, the value is always the latest and is visible to other threads.

  • When accessing a non-volatile variable, each thread copies the variable from the system memory (main memory) to the working memory, and then modifies the value of the variable in the working memory. The manipulated variable may be different.

4. How to disable instruction reordering optimization

volatile prohibits reordering optimization by setting a Java memory barrier.
Java memory barrier
Memory barrier, also known as memory barrier or barrier instruction, is a barrier instruction that allows the CPU or compiler to perform a sorting constraint on the memory operations issued before and after the barrier instruction. This usually means that operations issued before the barrier are guaranteed to be executed before operations issued after the barrier.
The four so-called memory barriers of java, namely LoadLoad, StoreStore, LoadStore, StoreLoad, are actually a combination of the above two, completing a series of barriers and data synchronization functions.
(Load command (that is, read from memory), Store command (that is, write to memory).)

  • LoadLoad barrier: For such statements Load1; LoadLoad; Load2, before
    the data to be read by Load2 and subsequent read operations are accessed, it is guaranteed that the data to be read by Load1 has been read.

  • StoreStore barrier: For such statements Store1; StoreStore; Store2, before Store2
    and subsequent write operations are executed, it is guaranteed that the write operations of Store1 are visible to other processors.

  • LoadStore barrier: For such statements Load1; LoadStore; Store2, before Store2
    and subsequent write operations are flushed out, ensure that the data to be read by Load1 has been read.

  • StoreLoad barrier: For such statements Store1; StoreLoad; Load2, before Load2
    and all subsequent read operations are executed, it is ensured that Store1 writes are visible to all processors. Its overhead is the largest of the four barriers. In most processor implementations, this barrier is a universal barrier, which has the functions of the other three memory barriers.

What does volatile do?
After a variable is modified by volatile, the JVM will do two things for us:

  • Insert StoreStore barrier before each volatile write operation and StoreLoad barrier after write operation. (StoreStore-Write-StoreLoad)

  • Insert a LoadLoad barrier before each volatile read operation, and insert a LoadStore barrier after the read operation. (LoadLoad-Read-LoadStore)

5. Volatile is not safe

Although volatile visibility ensures that all write operations to volatile variables can be immediately reflected in other threads (that is, volatile variables are consistent in each thread), the operations in Java are not atomic operations. Only volatile variables that are atomic operations are thread-safe, such as our very common variable ++ self-increment operation. In this process, the self-increment includes the operations of fetching, adding one, and saving the three processes, so the self-increment is combined It is not an atomic operation, and it is still unsafe to use volatile-modified variable increment operation.

for example:

public class MyVolitile {
    
    

    private static volatile int count = 0;

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 2; i++) {
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    for (int i = 0; i < 100; i++) {
    
    
                        count++;
                    }
                }
            }).start();
        }
        try {
    
    
            Thread.sleep(300);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        System.out.println(count);
    }
    
}

The result is different every time, not necessarily equal to 200.

6. Unsuitable scenarios for volatile

Since volatile variables can only guarantee visibility, in computing scenarios that do not meet the following two rules, we still have to lock (using synchronized or atomic classes in java.util.concurrent) to ensure atomicity.

  1. The result of the operation does not depend on the current value of the variable, or it can ensure that only a single thread modifies the value of the variable
  2. Variables do not need to participate in invariant constraints with other state variables

Guess you like

Origin blog.csdn.net/weixin_44395707/article/details/106688830