Implementation of shared variables in Java Concurrent Programming
visibility
If a thread changes the value of a shared variable and can be seen by other threads in time, it is called the visibility of shared variables
The Java virtual machine specification attempts to define a Java Memory Model (JMM) to shield the memory access differences of various hardware and operating systems, so that Java programs can achieve consistent memory access effects on various platforms
In short, because the CPU executes instructions very fast, but the speed of memory access is much slower, the difference is not an order of magnitude, so the big guys who make processors have added several layers of cache in the CPU
In the JAVA memory model, the above optimization is abstracted again JMM stipulates that all variables are stored in main memory, which is similar to the ordinary memory mentioned above. Each thread also contains its own working memory. It can be regarded as a register or cache on the CPU for easy understanding
Therefore, the operation of threads is mainly based on working memory. They can only access their own working memory, and the values should be synchronized back to the main memory before and after work
Simply put, when reading or modifying a shared variable in a multithread, it will first read the variable into its own working memory to become a copy. After making changes to the copy, it will be updated back to the main memory
Using working memory and main memory, although it speeds up, it also brings some problems For example, take the following example:
i = i + 1;
Suppose the initial value of I is 0. When only one thread executes it, the result must be 1. When two threads execute, will the result be 2? Not necessarily This may be the case:
线程1: load i from 主存 // i = 0 i + 1 // i = 1 线程2: load i from主存 // 因为线程1还没将i的值写回主内存,所以i还是0 i + 1 //i = 1 线程1: save i to 主存 线程2: save i to 主存
If two threads follow the above execution process, the final value of I is 1 If the last write back takes effect slowly and you read the value of I, it may be 0. This is the cache inconsistency problem
This situation is generally called invalid data. Because thread 1 has not written the value of I back to main memory, I is still 0. What is read in thread 2 is the invalid value (old value) of I
It can also be understood that after the operation is completed, the copy in the working memory is written back to the main memory, and the changes of shared variables are invisible to other threads until they synchronize variables from the main memory back to their own working memory
Order
Orderliness: that is, the program is executed in the order of the code For a simple example, look at the following code:
int i = 0; boolean flag = false; i = 1; //语句1 flag = true; //语句2
The above code defines an int variable and a boolean variable, and then assigns values to the two variables respectively
In terms of code sequence, statement 1 precedes statement 2. Will the JVM ensure that statement 1 will be executed before statement 2 when actually executing this code? Not necessarily. Why? Instruction reordering may occur here
Reorder
Instruction rearrangement refers to the reordering of existing instructions when the JVM compiles java code or when the CPU executes JVM bytecode
It does not guarantee that the execution sequence of each statement in the program is consistent with that in the code, but it will ensure that the final execution result of the program is consistent with that of the code (it means that the execution result of the program under a single thread is not changed)
Although the processor will reorder the instructions, it will ensure that the final result of the program will be the same as the result of code sequential execution. What guarantee does it rely on? Take another example:
int a = 10; //语句1 int r = 2; //语句2 a = a + 3; //语句3 r = a*a; //语句4
There are four statements in this code, so one possible execution order is:
So can it be this execution order?
Statement 2 statement 1 statement 4 statement 3
Impossible, because the processor will consider the data dependency between instructions when reordering. If an instruction instruction instruction 2 must use the result of instruction 1, the processor will ensure that instruction 1 will be executed before instruction 2
Although reordering does not affect the results of program execution in a single thread, what about multithreading? Here is an example:
//线程1: context = loadContext(); //语句1 inited = true; //语句2 //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context);
In the above code, statements 1 and 2 may be reordered because they have no data dependency
If reordering occurs, execute statement 2 first during the execution of thread 1. At this time, thread 2 will think that the initialization has been completed, so it will jump out of the while loop and execute the dosomething with config (context) method. At this time, the context is not initialized, which will lead to program errors
Atomicity
In Java, the reading and assignment operations of basic data types are atomic operations. The so-called atomic operations mean that these operations are non interruptible and must be completed or not executed
JMM only realizes the basic atomicity. For operations like I + +, synchronized and lock must be used to ensure the atomicity of the whole block of code Before releasing the lock, the thread must brush the value of I back to the main memory
Volatile keyword
Two layer semantics of volatile keyword
Once a shared variable (class member variable and class static member variable) is modified by volatile, it has two layers of semantics:
1) Instruction reordering is prohibited
2) When reading and writing a variable, it directly operates on the main memory
After a variable is modified by volatile, the JVM will do two things for us:
1. Insert the storestore barrier before each volatile write operation and the storeload barrier after the write operation
2. Insert the loadload barrier before each volatile read operation and the loadstore barrier after the read operation
Maybe this is a bit abstract. Let's take a look at the example of thread a code just now:
boolean contextReady = false; //在线程A中执行: context = loadContext(); contextReady = true;
What effect will it bring if we add the volatile modifier to contextready?
Due to the addition of the storestore barrier, the normal write statement context = loadcontext() above the barrier and volatile write statement contextready = true below the barrier cannot exchange order, thus successfully preventing instruction reordering
In other words, when the program executes the read or write operation of volatile variable, the changes of previous operations must have been made, and the results have been visible to subsequent operations
One of the volatile features: ensure the visibility of variables between threads The guarantee of visibility is the memory barrier instruction based on CPU, which is abstracted as the happens before principle by jsr-133
Volatile feature 2: prevent instruction rearrangement at compile time and run time At compile time, the JVM compiler follows the constraints of memory barrier, and the runtime relies on CPU barrier instructions to prevent rearrangement
The above is the whole content of this article. I hope it will help you in your study, and I hope you will support us a lot.