• 周六. 10 月 5th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Java Concurrent Programming: JMM (JAVA memory model) and detailed explanation with volatile keyword

King Wang

1 月 3, 2022

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 :
 Insert picture description here
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 :
 Insert picture description here
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 :

  1. Ensures that the decorated variable is visible to all threads .
  2. 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 》

发表回复