Lost two hairs, it can be regarded as volatile

本来想着快过年了偷个懒休息下,没想到被兄弟们连续催更,没办法,博主暖男嘛,掐着人中也要更,兄弟们卷起来

The volatile keyword can be said to be the most lightweight synchronization mechanism provided by the Java virtual machine, but as to why it can only guarantee visibility, not atomicity, and how it disables instruction rearrangement, there are still many students who have not thoroughly understand

Believe me, stick to reading this article, you will firmly grasp a core Java knowledge point

Let's talk about its two functions first:

  • Guaranteed visibility of variables in memory to threads
  • Disable instruction reordering

I know every word, it's numb when put together

These two functions are usually not easy for us Java developers to understand correctly and completely, so that many students cannot use volatile correctly

About visibility

Not much bb, code here

public class VolatileTest {
    
    
    private static volatile int count = 0;

    private static void increase() {
    
    
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < 10000; j++) {
    
    
                    increase();
                }
            }).start();
        }
		// 所有线程累加完成后输出
        while (Thread.activeCount() > 2) Thread.yield();
        System.out.println(count);
    }
}

The code is easy to understand. Ten threads are opened to accumulate the same shared variable count, and each thread accumulates 1w times.

We have decorated count with volatile, which has guaranteed the visibility of count to ten threads in memory. It stands to reason that the value of count should be 10w after ten threads are executed.

However, after running it many times, the results are far less than the expected value
insert image description here
. Which link is the problem?
insert image description here

You must have heard a sentence: volatile only guarantees visibility, not atomicity

This sentence is the answer, but many people still do not understand the mystery

It's a long story and I'll keep it short. In short, the count++ operation is not atomic, it is performed in three steps.

  1. Read the value of count from memory
  2. execute count + 1
  3. write the new value of count back

To fully understand this problem, we have to start with bytecode

The following is the bytecode compiled by the increase method.
insert image description here
It doesn't matter if you don't understand it, let's look at it line by line:

  1. GETSTATIC: read the current value of count
  2. ICONST_1: load constant 1 to the top of the stack
  3. IADD: +1 for implementation
  4. PUTSTATIC: Write the latest value of count

ICONST_1 and IADD are actually real ++ operations

The key point is that volatile can only ensure that the value obtained by the thread in the GETSTATIC step is the latest, but when the thread executes the following lines of instructions, other threads may modify the value of count during this period, which will eventually lead to The old value overwrites the real new value

understand me

Therefore, in concurrent programming, it is unreliable to rely only on volatile to modify shared variables. In the end, it is necessary to lock key methods to ensure thread safety.

Just like the demo above, a little modification can achieve true thread safety

The simplest is to add a synchronized to the increase method (I will not be verbose about how synchronized achieves thread safety. I have talked about the underlying implementation principle of synchronized before )

    private synchronized static void increase() {
    
    
        ++count;
    }

Run it a few times,
insert image description here
isn't it all right?

By now, you should have a new understanding of the following two points

  • volatile guarantees the visibility of the variable in memory to the thread
  • volatile only guarantees visibility, not atomicity

About instruction rearrangement

In concurrent programming, in order to improve execution efficiency, the CPU itself and the virtual machine will use instruction rearrangement (under the premise of ensuring that the results are not affected, some codes are executed out of order)

  • About cpu: in order to utilize cpu, the actual execution of instructions will be optimized;
  • About virtual machine: In HotSpot vm, in order to improve execution efficiency, JIT (just-in-time compilation) mode will also perform instruction optimization

Instruction rearrangement can indeed improve execution efficiency in most scenarios, but some scenarios are strongly dependent on the order of code execution. In this case, we need to disable instruction rearrangement. The
insert image description here
pseudocode for the following scenario is taken from "In-depth Understanding of Java Virtual Machine" :

The scenario described is a common configuration reading process in development, but we generally do not experience concurrency when processing configuration files, so we did not realize that this would be a problem.
Just imagine, if the volatile modification is not used when defining the initialized variable, the last code "initialized=true" in thread A may be executed in advance due to the optimization of instruction reordering (although Java is used as pseudo code here, all the Refers to the reordering optimization is a machine-level optimization operation, and early execution means that the assembly code corresponding to this statement is executed in advance), so that the code using configuration information in thread B may have errors, and volatile prohibits instruction rearrangement by you can avoid this from happening

Disabling instruction rearrangement only requires declaring the variable as volatile, isn't it magic?

Let's take a look at how volatile disables instruction reordering

Let's also borrow an example from "Understanding Java Virtual Machine", it is easier to understand.
insert image description here
This is the implementation of the singleton mode. The following is part of its bytecode. The red box mov%eax, 0x150(%esi) is the assignment to instance
insert image description here
It can be seen that after the assignment, the lock addl$0x0, (%esp) instruction is also executed. The key point is here. This line of instruction is equivalent to setting a memory . After the memory barrier, the cpu or virtual The machine cannot advance the instructions behind the memory barrier to the front of the memory barrier when the instructions are rearranged. Take a good look at this passage.


Finally, leave a question that can deepen everyone's understanding of volatile. Brothers, think about it:

The Java code is obviously executed from top to bottom. Why does the problem of instruction rearrangement occur?

ok i'm done

Guess you like

Origin blog.csdn.net/qq_33709582/article/details/122415754