More wonderful articles , Please pay attention to xhJaver, JD engineers grow up with you
volatile brief introduction
Commonly used to modify shared variables , Ensure visibility and prohibit command reordering
- When multithreading operates on the same variable , A thread has finished modifying , Other threads can immediately see the modified value , Guaranteed to share variables visibility
- Prohibit command rearrangement , Ensure that the code is executed Orderliness
- There is no guarantee of atomicity , For example, common i++
( But it's atomic for a single read or write )
Visibility code example
The following code suggests using PC Check it out , Copy paste runs directly , There are detailed notes
Let's write a code test , When multithreading modifies shared variables, do you need to use volatile Modifying variables
- First , We create a task class
public class Task implements Runnable{
@Override
public void run() {
System.out.println(" This is a "+Thread.currentThread().getName()+" Thread start ,flag yes "+Demo.flag);
// When the shared variable is true when , It's stuck here all the time , Don't output the following sentence
// When flag yes false when , Output the following sentence
while (Demo.flag){
}
System.out.println(" This is a "+Thread.currentThread().getName()+" Thread end ,flag yes "+Demo.flag);
}
}
2. secondly , Let's create a test class
class Demo {
// Shared variables , Not yet. volatile modification
public static boolean flag = true ;
public static void main(String[] args) throws InterruptedException {
System.out.println(" This is a "+Thread.currentThread().getName()+" Thread start ,flag yes "+flag);
// Open the thread just now
new Thread(new Task()).start();
try {
// Sleep for a second , Make sure that the thread just now ran to while loop
// Or it hasn't run to while loop , The main thread will flag Turn into false
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Changing shared variables flag To false
flag = false;
System.out.println(" This is a "+Thread.currentThread().getName()+" Thread end ,flag yes "+flag);
}
}
3. Let's look at the output
so , The program doesn't end , He's stuck here , Why is it stuck here , It's because we changed the shared variables in the main thread flag by false, But another thread didn't perceive , Changes to this variable are not visible to another thread
- If you use volatile Variable modification , It turns out to be like this
public static volatile boolean flag = true
so , This time, the variables modified by the main thread are perceived by another thread , Guarantee the variable's visibility
Visibility principle analysis
that , magical volatile What the hell is going on at the bottom , Your change , He can't escape his eye ? Why don't you use him to modify variables , The change of variables is invisible to other threads ?
Answer this question first , We need to know JMM(Java Memory model )
notes : Local memory is JMM An abstraction of , It's not real , Local memory it covers caching , Write buffer , After that, the location of the compiler and other hardware registers is optimized
So we can analyze , The main thread modified the variable , But other threads don't know , There are two situations
- The variables modified by the main thread have not been flushed to the main memory yet , The other thread reads the previous variable
- The variables modified by the main thread are flushed to the main memory , But other threads are reading local copies
When we use
volatile
Keyword modification of shared variables can do the following two things- When a thread modifies a variable , Will force refresh to the main memory
- When a thread reads a variable , It forces the variables to be read from main memory and flushed to working memory
Command rearrangement
- What is instruction rearrangement ?
In order to improve the efficiency of the program , The compiler and cpu It rearranges the order in which the code is executed , But sometimes it brings a lot of problems
Let's look at the code
// Instruction rearrangement test
public class Demo2 {
private Integer number = 10;
private boolean flag = false;
private Integer result = 0;
public void write(){
this.flag = true; // L1
this.number = 20; // L2
}
public void reader(){
while (this.flag){ // L3
this.result = this.number + 1; // L4
}
}
}
If we have A、B Two threads They execute separately write() Methods and reader() Method , The order of execution may be as shown in the following figure
- Problem analysis : As shown in figure shows ,A Thread L2 and L1 Has been reordered , If it does, then , When A After execution L2 when ,B Start execution L3, But this time flag Still for false, that L4 It can't be executed , therefore result The value of is still the initial value 0, Not changed to 21, Cause program execution error
This is the time , We can use it volatile
Keyword to solve this problem , It's simple , just
private volatile Integer number = 10;
- This is the time L1 It must be L2 Go ahead
A The thread is modifyingnumber
Variable is 20 When , Just make sure that the code in front of this code must be executed before this line of code , staynumber
It's inserted at Memory barrier , In order to achieve volatile Memory semantics of , When the compiler generates bytecode , Memory barriers are inserted into the instruction sequence to prevent certain types of processor rearrangements
Memory barrier
What is the memory barrier ? There are four types of memory barriers in total , They are
LoadLoad barrier :
- Load1 LoadLoad Load2 Make sure Load1 The loading of data for is prior to Load2 And all subsequent loading instructions loading
LoadStore barrier :
- Load1 LoadStore Store2 Make sure Load1 The loading of data for is prior to Store2 And all subsequent storage instructions storage
StoreLoad barrier :
- Store1 StoreLoad Load2 Make sure Store1 The data of is visible to other processors ( Refresh to memory ) Precede Load2 And all subsequent loading instructions loading
StoreStore barrier :
- Store1 StoreStore Store2 Make sure Store1 Data is visible to other processors ( Refresh to memory ) Precede Store2 And all subsequent storage instructions storage
> StoreLoad It's an all powerful barrier , At the same time, it has other 3 The effect of a barrier . The cost of executing the barrier is more expensive , Because the processor usually flushes the contents of the current write buffer to memory (Buffer Fully Flush)
- load load It's reading int a = load1 ( load1 The loading of )
- Storage store Is to write store1 = 5 ( store1 The storage )
volatile With the memory barrier
that volatile What does it have to do with these four memory barriers , How to insert it ?
volatile Write ( Insert barriers in front and back )
- Insert a before StoreStore barrier
- Insert a StoreLoad barrier
volatile read ( Just insert a barrier in the back )
- Insert a LoadLoad barrier
- Insert a LoadStore barrier
The official form is like this
We're looking back at our program
this.flag = true; // L1
this.number = 20; // L2
because number By volatile Modify the ,L2 This sentence is volatile Write , This is what the barrier should look like
this.flag = true; // L1
// StoreStore Make sure flag Data is visible to other processors ( Refresh to memory ) Precede number And all subsequent storage instructions storage
this.number = 20; // L2
// StoreLoad Make sure number Data is visible to other processors ( Refresh to memory ) Load before all subsequent storage instructions
therefore L1,L2 The execution order of is not reordered
ps: The fourth building of headquarters is getting better and better , Reward yourself with a cup of milk tea
More exciting , Please pay attention to the official account. xhJaver, JD engineers grow up with you
Past highlights
- ? Why can thread pools be reused , I'm covered up ..
- actual combat !xhJaver Even with thread pool optimization ...
- Won't! , That's how you make the thread pool so clear ?
- mysql You can rely on the index , And I have to work , come on. , Workers !
- So you are xhJaver!( The article of wandering heart )
- Will you do this interview question of Jingdong ?