List of articles
-
- Consistency of computer systems
- Java Memory model
- Memory model 3 Important features
-
- Atomicity
- visibility
- Orderliness
- Instruction reordering
- volatile keyword
-
- Ensures visibility and prevents reordering of instructions
- Atomicity is not guaranteed
Consistency of computer systems
In modern computer operating systems , Multitasking is almost an essential feature , Because it’s embedded in a multi-core processor , A computer system is truly capable of performing several tasks at once , It’s really a multicore system . In a multicore system , In order to improve CPU Interaction efficiency with memory , There is usually a layer “ Cache ” As a buffer between memory and the processor , bring CPU Read data directly from the cache during the operation , This solves the performance problem to some extent . however , This raises a new problem , Namely “ Cache consistency ” The problem of . such as , In the multicore case , Each processor has its own cache , How is the data consistent . In response to this question , Modern computer systems introduce multiprocessor protocols for data consistency , Include MOSI、Synapse、Firely、DragonProtocol etc. .
When the processor interacts with main memory through the cache , Data must be read and written according to the standards specified in the protocol , In a diagram, it looks something like this :
and Java Memory model (JMM) It is similar to the consistency model of hardware , The thread communication mechanism of Shared memory is adopted .
Java Memory model
Java The memory model specifies that all variables are stored in main memory , Each thread has its own working memory , A copy of the main memory copy of the variable used by the thread is held in working memory , A thread can only manipulate copies of variables in its own working memory , After the variables are manipulated, they are updated to main memory , The main memory is used to pass the values of variables to other threads . The interaction of this model is shown in the following figure :
However ,Java The memory model only reflects the thread handling mechanism inside the virtual machine , The concurrency security of the program itself is not guaranteed .
For example , Augmenting a Shared variable in a program :
i++;
So let’s say it’s initialized i=0, When running to this program , The thread first reads from main memory i Value , Then copy to your own working memory , Conduct i++ operation , Finally, the result of the operation is copied from the working memory to the main memory . If you have two threads executing i++ The program , The expected result is 2. But is it ? The answer is No .
Assuming that thread 1 Reads from main memory i=0, Copy to your own working memory , It’s going on i++ Has not been updated to main memory since the operation , When a thread 2 Also read i=0, We did the same thing , So what we end up with is zero 1, instead of 2.
This is a typical example of concurrency safety with multiple threads , It’s also Java One of the most interesting topics in concurrent programming , Generally speaking , There are two ways to deal with this problem :
- Lock , Like the way you synchronize blocks of code . Ensure that only one thread can execute at a time i++ This program .
- Take advantage of communication between threads , Like using objects wait and notify The method is to .
Because this article is mainly about exploration JMM and volatile Keyword knowledge , How do you implement concurrent processing without going into the details , Take a look at some other time and write a blog post on it .
Memory model 3 Important features
What is the preliminary understanding JMM after , Let’s take a closer look at its important features . It’s worth noting that , stay Java Multi-threaded development , It follows three basic characteristics , atomicity 、 Visibility and orderliness , and Java Is the memory model built around how to deal with these three characteristics in the process of concurrency .
Atomicity
Atomicity means that the operation is atomic , uninterruptible . for instance :
String s="abc";
This operation is a direct assignment , Atomic operations . Code like this is not atomic :
i++;
When executed i++ when , You need to get i Value , And then execute i+1, There are two operations involved , So it’s not atomic .
visibility
Visibility is when data is Shared , A thread modifies the data , Other threads know that the data has been modified , The latest main memory data is re-read . Just like the two threads mentioned earlier i++ The problem of , Threads 1 Did not update to main memory after changing , So threads 2 I don’t know .
Orderliness
It refers to the orderliness of code execution , Code that executes for a thread , We can think of the code as being executed in sequence , But in concurrency, you might have disorder , Because it is possible for the code to be reordered (Instruction Reorder), The rearranged instruction may not be in the same order as the original instruction .
Instruction reordering
The compiler is free to change the instruction order in the name of optimization . Under certain circumstances , The processor may execute instructions in reverse order . Is a reorder for the instruction , Especially in the case of concurrency .
java Provides volatile and synchronized To ensure the orderliness of operations between threads .volatile Contains semantics that prohibit instruction reordering ( That is, its second semantics ),synchronized Specifies that only one thread is allowed on a variable at a time lock operation , That is, two synchronized blocks of the same lock can only enter serially . Reordering of instructions is disabled .
volatile keyword
That’s it volatile, It’s important to know what this keyword does .
To be precise ,volatile yes java Provides a lightweight synchronization mechanism . It has two properties :
- Ensures that the decorated variable is visible to all threads .
- Disables instruction reorder optimization .
Ensures visibility and prevents reordering of instructions
Simply write a piece of code to explain it :
public class VolatileDemo {
private static boolean isReady;
private static int number;
private static class ReaderThread extends Thread{
@Override
public void run() {
while (!isReady);
System.out.println("number = "+number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
try {
Thread.sleep(1000);
number = 42;
isReady = true;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In the code above ,ReaderThread
Only in isReady
by true It will print out when number
Value , However , The real situation may not be printed ( Less likely , But there are still ), Because the thread ReaderThread The thread cannot see the pair in the main thread isReady
Modification of , Lead to while The loop never exits , meanwhile , Because of the possibility of reordering , Causes the following code to not be executed sequentially :
number = 42;
isReady = true;
That is, if it can be printed ,number The value could be 0, No 42. If I add to the variable volatile keyword , tell Java These two variables may be modified by different threads , So you can prevent the occurrence of the above two kinds of abnormal situation .
Atomicity is not guaranteed
volatile It ensures visibility and order , But atomicity is not guaranteed , Take the following example :
public class VolatileDemo {
public static volatile int i = 0;
public static void increase() {
i++;
}
public static void main(String[] args) throws InterruptedException {
VolatileDemo test = new VolatileDemo();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++)
test.increase();
}).start();
}
Thread.sleep(1000);
System.out.println(test.i);
}
}
Under normal circumstances , We expect the top main The output after the function is executed is 10000, But you’ll see , It’s always going to be less than 10000, because increase() Methods i++
The operation is not atomic , There are two operations: read and write . Suppose that when threads 1 Read out i Value , It hasn’t been modified yet , Threads 2 This is when the read is done . then , Threads 1 It’s finished , Notification thread 2 Reread i Value , But then it doesn’t need to read i, It still performs write operations , Then assign the value to the main thread , That’s where the data goes wrong .
therefore , Read and write operations for Shared variables in general , Locks are still needed to guarantee results , For example, add synchronized keyword .
Reference resources :
《Java High concurrency programming 》
《 In depth understanding of Java virtual machine 》