在前文中已经讲解到volatile的使用以及原子性的问题,volatile修饰的变量可以实现线程对变量的写操作能够让其他线程“看到”当前最新的变量数据值,从内存语义上而言,相当于告诉线程读取当前变量要从主内存中读取,对此,现将继续前文的volatile继续深入原理分析.
volatile规则与作用
使用volatile的场景
部分源码
// shared.java
volatile boolean finished = false;
producer(){
TimeUnit.SECONDS.sleep(1);
finised = true;
System.out.println("have finished product done ....");
}
consumer(){
while (!finised){
// nothing
}
System.out.println("have consume product done " + full);
}
// producer.java
// 代码仅做演示,忽略其他因素
run(){
producer();
}
// consumer.java
run(){
consumer();
}
基于上述代码内存演示图
内存屏障是处理器层面进行的,因此这里直接查阅jvm下的部分cpu架构源码对volaitle的内存屏障进行说明
关于ARM(指令集机器指令)参考说明
JVM中的aarch64架构处理器对volatile的内存屏障说明
ldar<x>
表示volatile的读指令, stlr<x>
表示volatile的写指令// AArch64 has ldar<x> and stlr<x> instructions which we can safely
// use to implement volatile reads and writes. For a volatile read
// we simply need
//
// ldar<x>
//
// and for a volatile write we need
//
// stlr<x>
// 读屏障
// Alternatively, we can implement them by pairing a normal
// load/store with a memory barrier. For a volatile read we need
//
// ldr<x> // 读取volatile数据
// dmb ishld // 内存屏障
// 写屏障
// for a volatile write
//
// dmb ish // 内存屏障
// str<x> // 写volatile数据
// dmb ish // 内存屏障
// demo.java
volatile int j = 0;
//threadA write
run(){
// ...code start ...
j = 9;
// ... code end ...
}
//threadB read
run(){
// ... code start ...
a = j;
a ++;
// ... code end ...
}
// 写屏障
threadA run(){
//... code start ...
dmb ish // 防止上面代码与写volatile代码重排
str<i> // j = 9,由于是写屏障,所以会使缓存失效,并更新到主内存中
dmb ish // 防止下面代码与写volatile代码重排
//... code end ....
}
// 读屏障
threadB run(){
// ... code start ...
ldr<x> // 读取j的数据
dmb ishld // 内存屏障,防止与下面的a++重排序
a++;
// ... code end ...
}
问题思考
就是前文说明有一个-server服务端模式,编译器会对代码进行重排序,而这里说的volatile因为内存屏障而没有重排序不会矛盾?
重排序有编译器重排序,也有CPU处理器重排序,内存屏障是在CPU层面进行的,也就是说使用volatile修饰的变量仍然会在编译阶段导致代码重排,但是字节码转换为机器码的时候会识别有带volatile的修饰,因此会增加内存屏障来防止处理器进行重排序,两者并不矛盾
总结
感谢花时间阅读,如果有用,您的转发或好看是对我最大的支持,谢谢!