This is the 9th day of my participation in Gwen Challenge
CPU Cache model and JMM (Java Memory Mode)
To understand the effect of volatile, we need to understand the relationship between the hardware CPU cache model and the Java memory model:
1.Cpu Cache model:
Cache speed: Register >L1>L2>L3> main memory. Some shared data is put in the main memory. The CPU reads data from the main memory to the three-level cache and then modifies the register, and then refreshes the modified data to the main memory. At this time, we thought, if there are two cpus, both need to modify the same data, will it cause data security problems? The usual solution is through the BUS or EMSI protocol, which is more granular and costly than EMSI.
The EMSI protocol is Exclusive, Modified, S, and Invalid. If the Cache line is E or S, the data in the Cache is the same as the data in the main memory. If the Cache line is unique, the data exists only in the current Cache. If the Cache line is shared, it also exists in other caches. If the status of the Cache line is M, it indicates that it has been modified in the local Cache. Other caches have a listening mechanism, and the Cache line becomes I invalid. When the modified data is flushed to main memory, the Cache line of other threads changes to E or S.
2.Java memory model
Start by stating that the Java memory model is an abstract concept that does not exist in physical space. The idea is similar to the CPU cache model. If a thread wants to modify a piece of data, it will first go to the workspace to see if it has the data. If it does not, it will go to main memory to find the data. Of course, you’re smart enough to think about data security.
3. Java memory model and CPU cache model:
Both workspace and main memory data can come from registers, caches, and main memory in the Cpu cache model.
Ii. The Effects of Volatile
1. Volatile is used to modify variables:
private static volatile int init_value = 0;
Copy the code
Volatile has the effect of preventing instruction reordering, which is one of the happens-before principles. In short, the JVM optimizes the code we write, and the execution order of the code can be scrambled. Volatile is used, and the execution order of the variables before and after volatile cannot be changed, so it is ordered. It also ensures visibility of the data, so that when one thread modifies the data, other threads will sniff out that the data has been changed, meaning that the data has been changed to be visible to other threads.
We can get assembly code with HSDIS and JITWatch:
. 0x0000000002951563: and$0xffffffffffffff87,%rdi
0x0000000002951567: je 0x00000000029515f8
0x000000000295156d: test $0x7,%rdi
0x0000000002951574: jne 0x00000000029515bd
0x0000000002951576: test $0x300,%rdi
0x000000000295157d: jne 0x000000000295159c
0x000000000295157f: and $0x37f,%rax 0x0000000002951586: mov %rax,%rdi 0x0000000002951589: or %r15,%rdi 0x000000000295158c: Lock CMPXCHG %rdi,(% RDX) 0x0000000002951591 CMPXCHG %rdi,(% RDX) jne 0x0000000002951a15 0x0000000002951597: jmpq 0x00000000029515f8 0x000000000295159c: mov 0x8(%rdx),%edi 0x000000000295159f: shl$0x3,%rdi
0x00000000029515a3: mov 0xa8(%rdi),%rdi
0x00000000029515aa: or %r15,%rdi
......
Copy the code
Iii. Application Scenarios of Volatile
1. Use visibility to do read/write separation:
VolatileDemo public class VolatileDemo {private static final int MAX = 5; private static volatile int init_value = 0; Public static void main(String[] args) {// Read new Thread(()->{int localValue = init_value;while (localValue<MAX){
if(localValue ! = init_value){ System.out.printf("init_value is update to %d \n",init_value); localValue = init_value; }}},"Reader").start(); // write new Thread(()->{int localValue = init_value;while (localValue<MAX){
System.out.printf("init_value will change to %d \n",(++init_value)); localValue =init_value; try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }}},"Writer").start(); }}Copy the code
The results are as follows:
init_value will change to 1
init_value is update to 1
init_value will change to 2
init_value is update to 2
init_value will change to 3
init_value is update to 3
init_value will change to 4
init_value is update to 4
init_value will change to 5
init_value is update to 5
Copy the code
2. Use visibility as a switch
Public class ShutdownDemo extends Thread{private volatile static Boolean flag =true;
@Override
public void run() {
doWork();
}
private void doWork() {
while (flag){
System.out.println("I am working.....");
}
}
private void shutDown(){
try {
TimeUnit.SECONDS.sleep(2);
flag = false; } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ShutdownDemo shutdownDemo = new ShutdownDemo(); New Thread(shutdownDemo).start(); new Thread(shutdownDemo::shutDown).start(); }Copy the code
Operation result: stop working after 2s
I am working.....
I am working.....
I am working.....
I am working.....
I am working.....
Process finished with exit code 0
Copy the code
Application Scenario 3: Double Check Lock (DCL) singleton mode
public class DCLDemo {
private volatile static DCLDemo dclDemo;
private static DCLDemo getDclDemo() {if (dclDemo==null){
synchronized (DCLDemo.class){
if(dclDemo==null){ dclDemo = new DCLDemo(); }}}return dclDemo;
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++) { new Thread(()-> System.out.println(getDclDemo())).start(); }}}Copy the code
PS: Elegant lazy loading (static inner classes, and of course enumerations, etc.)
public class Singleton {
static class SingletonHolder {
static Singleton instance = new Singleton();
}
public static Singleton getInstance() {returnSingletonHolder.instance; }}Copy the code
4. Disadvantages of Volatile:
You can’t guarantee atomicity, so to ensure atomicity, see another article I wrote, Synchronize:
Public class VolatileIncreaseDemo {private static volatile int I =0; VolatileIncreaseDemo public class VolatileIncreaseDemo {private static volatile int I =0; private static voidincrease(){
i++;
}
public static void main(String[] args) {
for (int j = 0; j < 10; j++) {
new Thread(()->{
for(int k = 0; k < 5; k++) { increase(); } }).start(); } System.out.println(i); }}Copy the code
The result is less than 50:
20
Process finished with exit code 0
Copy the code