当前位置:网站首页>The core concept of JMM: happens before principle

The core concept of JMM: happens before principle

2022-06-24 22:40:00 Java enthusiast

JMM Designers' problems and perfect solutions

in fact , from JMM From the designer's point of view , Visibility and order are actually two contradictory points :

  • One side , For programmers , We want the memory model to be easy to understand 、 Easy to program , So JMM Designers should provide programmers with a strong enough memory visibility guarantee , In technical terms, it's called “ Strong memory model ”.
  • And on the other hand , Compilers and processors want the memory model to constrain them as little as possible , So they can do as many optimizations as possible ( Like reordering ) To improve performance , therefore JMM Designers of the compiler and processor restrictions to be relaxed as much as possible , In technical terms, it's called “ Weak memory model ”.

For this question , from JDK 5 Start , That is to say JSR-133 In memory model , Finally, a perfect solution is given , That's it  Happens-before  principle ,Happens-before Literal translation “ Happen first ”,《JSR-133:Java Memory Model and Thread Specification》 Yes Happens-before The definition of a relationship is as follows :

1) If an operation Happens-before Another operation , Then the execution result of the first operation will be visible to the second operation , And the execution order of the first operation is before the second operation .

2) There is... Between the two operations Happens-before Relationship , It doesn't mean that Java The specific implementation of the platform must be in accordance with Happens-before The relationship is executed in the order specified . If the execution result after reordering , And press Happens-before The result of the relationship is the same , So this sort of reordering is not illegal ( in other words ,JMM Allow this reorder )

It's not hard to understand , The first 1 The first definition is JMM Commitment to programmer's strong memory model . From a programmer's point of view , It can be understood in this way Happens-before Relationship : If A Happens-before B, that JMM The programmer will be assured that — A The result of the operation will be B so , And A The order of execution is B Before . Be careful , This is just Java The memory model guarantees the programmer !

It should be noted that , differ as-if-serial Semantics can only work on a single thread , The two operations mentioned here A and B It can be within a thread , It can also be between different threads . in other words ,Happens-before Provide Cross thread memory visibility guarantee .

For this second 1 Definitions , Let me give you an example :

//  The following operations are in thread  A  In the implementation of 
i = 1; // a

//  The following operations are in thread  B  In the implementation of 
j = i; // b

//  The following operations are in thread  C  In the implementation of 
i = 2; // c

Assuming that thread A The operation a Happens-before Threads B The operation of b, Then we can confirm the operation b After execution , Variable j The value of must be equal to 1.

There are two reasons for this conclusion : One is based on Happens-before principle ,a The result of the operation is right b so , namely “i=1” The results can be observed ; The two is thread. C It's not running yet , Threads A No other thread will change the variables after the operation i Value .

Now consider threads C, We still keep a Happens-before b , and c Appear in the a and b Between operations , however c And b No, Happens-before Relationship , in other words b It's not necessarily possible to see c The result of the operation . that b The result of the operation is j I'm not sure , May be 1 It could be 2, This code is thread unsafe .

Look again. Happens-before Of the 2 Definitions , This is a JMM Guarantees for compiler and processor weak memory models , Given sufficient operational space , There are certain constraints on the reordering of compilers and processors . in other words ,JMM It's actually following a basic principle : As long as the execution result of the program is not changed ( It refers to single thread program and correctly synchronized multithreaded program ), Compiler and processor can be optimized .

JMM And the reason for that is : Programmers don't care if these two operations are really reordered , The programmer's concern is that the execution result cannot be changed .

Words may not be easy to understand , Let's take an example , To explain the next 2 Definitions : Although there is a gap between the two operations Happens-before Relationship , But it doesn't mean Java The specific implementation of the platform must be in accordance with Happens-before The relationship is executed in the order specified .

int a = 1; 		// A
int b = 2;		// B
int c = a + b;	// C

according to Happens-before The rules ( I'll talk about ), The above code exists 3 individual Happens-before Relationship :

  • A Happens-before B
  • B Happens-before C
  • A Happens-before C

You can see it , stay 3 individual Happens-before In relationship , The first 2 And the first 3 One is necessary , But the first 1 One is unnecessary .

in other words , although A Happens-before B, however A and B Reordering between will not change the execution result of the program at all , therefore JMM It allows the compiler and processor to perform this reordering .

Look at this one JMM It's more intuitive :

Actually , It can be understood so simply , for fear of Java Programmers to understand JMM The memory visibility guarantees the learning of complex reordering rules and the specific implementation methods of these rules ,JMM There is such a simple and easy to understand Happens-before principle , One Happens-before Rules correspond to reordering rules for one or more compilers and processors , such , We just need to figure out Happens-before That's it .

8 strip Happens-before The rules

《JSR-133:Java Memory Model and Thread Specification》 It is defined as follows Happens-before The rules , These are the JMM in “ natural ” Happens-before Relationship , these Happens-before Relationships exist without any synchronizer assistance , Can be used directly in coding . If the relationship between two operations is not in this column , And can't be derived from the following rules , Then they have no sequential guarantee ,JVM You can reorder them at will :

1) Program Order Rule (Program Order Rule): In a thread , In the order of control flow , Writing takes place first (Happens-before) The operation of writing at the back . Be careful , This is about the sequence of control flow, not the sequence of program code , Because we need to think about branches 、 Circulation and other structures .

It's easy to understand , In line with our logical thinking . For example, the example we mentioned above :

int a = 1; 		// A
int b = 2;		// B
int c = a + b;	// C

According to the order of procedure , The above code exists 3 individual Happens-before Relationship :

  • A Happens-before B
  • B Happens-before C
  • A Happens-before C

2) Tube lock rule (Monitor Lock Rule): One unlock The operation occurs first of all on the same lock lock operation . It must be emphasized here that “ Same lock ”, and “ Back ” It refers to the order of time .

This rule is aimed at synchronized Of .JVM Not yet  lock  and  unlock  Operation is directly open to users , But it provides a higher level of bytecode instructions  monitorenter  and  monitorexit  To implicitly use these two operations . These two bytecode instructions reflect Java In the code is the synchronization block — synchronized.

for instance :

synchronized (this) { //  Lock automatically here 
	if (x < 1) {
        x = 1;
    }      
} //  It will unlock automatically here 

According to the tube side locking rules , hypothesis x The initial value of 10, Threads A After executing the code block x The value of will become 1, Automatic release lock after execution , Threads B When entering a code block , Able to see threads A Yes x Write operations for , So threads B Be able to see x == 1.

3)volatile Variable rule (Volatile Variable Rule): To a volatile The write operation of the variable first occurs after the read operation of the variable , there “ Back ” It also refers to the order of time .

The rule is JDK 1.5 Version pair volatile Semantic enhancement , It's of great significance , It's easy to get visibility done with this rule .

for instance :

Assuming that thread A perform writer() After method , Threads B perform reader() Method .

According to the order of procedure :1 Happens-before 2;3 Happens-before 4.

according to volatile Variable rule :2 Happens-before 3.

According to the transitivity rules :1 Happens-before 3;1 Happens-before 4.

in other words , If the thread B I read “flag==true” perhaps “int i = a” So thread A Set it to “a=42” For threads B Is visible .

Look at the picture below :

4) Thread start rule (Thread Start Rule):Thread Object's start() Method occurs first in every action of this thread .

For example, the main thread A Start the child thread B after , Sub thread B You can see that the main thread is starting the sub thread B All operations before .

5) Thread termination rule (Thread Termination Rule): All operations in a thread occur first at the termination detection of this thread , We can go through Thread Object's join() Whether the method ends 、Thread Object's isAlive() Check whether the thread has terminated the execution .

6) Thread interrupt rule (Thread Interruption Rule): For threads interrupt() Method calls occur first when the interrupted thread's code detects the occurrence of an interrupt event , Can pass Thread Object's interrupted() Method detects if an interrupt has occurred .

7) Object termination rule (Finalizer Rule): Initialization of an object completed ( End of constructor execution ) What happened first finalize() The beginning of the method .

8) Transitivity (Transitivity): If you operate A First occurs in operation B, operation B First occurs in operation C, Then we can get the operation A First occurs in operation C Conclusion .

“ First in time ” And “ Happen first ”

Above 8 In two rules , Also mentioned time successively , that ,“ First in time ” And “ Happen first (Happens-before)” What's the difference ?

One operation “ First in time ” Does it mean that this operation will be “ Happen first ” Well ? One operation “ Happen first ” Can we deduce that this operation must be “ First in time ” Well ?

unfortunately , Neither of these inferences holds true .

Give two examples to demonstrate :

private int value = 0;

//  Threads  A  call 
pubilc void setValue(int value){    
    this.value = value;
}

//  Threads  B  call 
public int getValue(){
    return value;
}

Suppose there are threads A and B, Threads A First ( The order of time ) Called setValue(1), Then the thread B Calling... Of the same object getValue() , So thread B What is the return value received ?

According to the above Happens-before Of 8 The big rules are analyzed in turn :

Because the two methods are separated by threads A and B call , Not in the same thread , So the procedural order rule doesn't apply here ;

Because there is no  synchronized  Synchronized block , It doesn't happen naturally lock and unlock operation , So the tube lock rule doesn't apply here ;

alike ,volatile  Variable rule , Thread start 、 End 、 Interruption rules and object termination rules have nothing to do with this .

Because there's no one that works Happens-before The rules , So the first 8 There is no way to talk about the transitivity of these rules .

So we can judge , Although thread A In terms of operation time, it is prior to thread B Of , But it can't be said that A Happens-before B, That is to say A The result of thread operation B Not necessarily visible . therefore , This code is thread unsafe .

It's easy to fix this problem ? Since I'm not satisfied Happens-before principle , Then I'll modify it to make it satisfied . Let's say a Getter/Setter All methods are used  synchronized  modification , In this way, the pipe locking rules can be applied ; Take... For example value Defined as  volatile  Variable , In this way, you can apply volatile Variable rules, etc .

This example , It is argued that One operation “ First in time ” Doesn't mean this operation will be “ Happen first (Happens-before)”.

Let's take another example :

//  The following operations are performed in the same thread 
int i = 1;
int j = 2;

Suppose that the two assignment statements in this code are in the same thread , So according to the order of procedure ,“int i = 1” The operation of the first occurrence (Happens-before) On “int j = 2”, however , Remember Happens-before Of the 2 Is there a definition ? I still remember that JMM In fact, it is to abide by such a principle : As long as the execution result of the program is not changed ( It refers to single thread program and correctly synchronized multithreaded program ), Compiler and processor can be optimized .

therefore ,“int j=2” This code is likely to be executed by the processor first , Because it doesn't affect the final result of the program .

that , This example , It is argued that One operation “ Happen first (Happens-before)” It doesn't mean that this operation must be “ First in time ”.

such , To sum up, there are two cases , We can come to the conclusion that :Happens-before There is basically no causal relationship between principle and time sequence , So when we measure concurrency security , Try not to be disturbed by the time sequence , Everything must be done with Happens-before The principle shall prevail .

Happens-before And as-if-serial

Sum up , I think I can understand the following sentence Happens-before 了 , This sentence has appeared several times before :JMM It's actually following a basic principle , That is, as long as the execution result of the program is not changed ( It refers to single thread program and correctly synchronized multithreaded program ), Compiler and processor can be optimized .

Look back as-if-serial semantics : No matter how reorder , The result of program execution cannot be changed in single thread environment .

Did you find that ? In essence Happens-before Relationship and as-if-serial Semantics is one thing , It's all for the sake of not changing the result of program execution , Improve the parallelism of program execution as much as possible . But the latter only works on a single thread , The former can work in a multi-threaded environment with correct synchronization :

  • as-if-serial Semantics ensure that the execution result of a program in a single thread is not changed ,Happens-before The relationship ensures that the execution result of a multithreaded program that is correctly synchronized is not changed .
  • as-if-serial Semantics create an illusion for programmers who write single threaded programs : Single threaded programs are executed in the order of programs .Happens-before Relationships create an illusion for programmers who write multithreaded programs that are properly synchronized : The correct synchronization of multithreaded programs is by pressing Happens-before In a specified order .
原网站

版权声明
本文为[Java enthusiast]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/175/202206241637502529.html