1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > java内存屏障详解_图文带你了解volatile底层和内存屏障之间的关系

java内存屏障详解_图文带你了解volatile底层和内存屏障之间的关系

时间:2024-08-17 11:13:58

相关推荐

java内存屏障详解_图文带你了解volatile底层和内存屏障之间的关系

欢迎大家搜索“小猴子的技术笔记”关注我的公众号,有问题可以及时和我交流。

LoadLoad Barriers:在两个读指令之间插入一个“LoadLoad”的内存屏障,确保Load1的数据装载,先于Load2的数据装载。

StoreStore Barriers:在两个写指令之间插入一个“StoreStore”的内存屏障。确保Store1的数据先刷新到主内存,并且对其数据可见。Store1的写数据先于Store2的写数据。

LoadStore Barriers:在读和写指令之间加一个“LoadStore”屏障,确保Load1的数据装载先于Store2的写数据。

StoreLoad Barriers:在写和读之间加一个“StoreLoad”屏障,确保Store1的数据写入并且刷新到内存先于Load2。“StoreLoad”会使该屏障之前所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。执行“StoreLoad”屏障的开销比较昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(不了解写缓冲区概念的小伙伴,可以查看我上篇文章《为什么会有重排序?它对线程有什么影响?》)。

如果你简历上写的有多线程的知识的话,那么面试官很大几率会问你volatile这个关键字的问题。也许你会说出,volatile是解决了内存可见性问题和禁止重排序的作用。那么你知道它底层是怎么解决的吗?

为了实现volatile的内存语义,编译器在生成字节码的时候,JMM采取保守策略会向指令序列中插入内存屏障来禁止特定类型的处理器重排序。

1.在每个volatile写操作前面插入一个StoreStore屏障。

2.在每个volatile写操作后面插入一个StoreLoad屏障。

3.在每个volatile读操作后面插入一个LoadLoad屏障。

4.在每个volatile读操作后面插入一个LoadStore屏障。

下图将对保守策略的内存屏障做一个关系的解读:

注意:上述的volatile写和volatile的读的内存屏障插入策略非常保守。其实在实际执行时,只要不改变volatile写-读的内存语义,编译器就可以根据具体情况省略不必要的屏障。比如下面的这个例子:

public class VolatileBarriersExample {

int a;

volatile int v1 = 1;

volatile int v2 = 2;

void readAndWrite() {

// 第一个volatile读

int i = v1;

// 第二个volatile读

int j = v2;

// 普通写

a = i + j;

// 第一个volatile写

v1 = i + 1;

// 第二个volatile写

v2 = j * 2;

}

}

注意,最后的StoreLoad屏障不能省略,因为第二个volatile写之后,方法立即返回。此时编译器无法准确判断后面是否会有volatile读或写。为了安全起见,编译器通常会在这里插入一个StoreLoad屏障。其实,volatile禁止指令重排序就是使用了内存屏障作为保证来实现的。

了解volatile的底层内存屏障的实现之后,我们来看一下对一个volatile变量的读写时,该共享变量所在的本地内存和主内存的变化(也就是内存可见性的问题):

public class VolatileExample {

int a = 0;

volatile boolean flag = false;

public void writer() {

// 第一步

a = 1;

// 第二步

flag = true;

}

public void reader() {

// 第三步

if (flag) {

// 第四步

int i = a;

System.out.println(i);

}

}

}

假设线程A首先执行了writer()方法,随后线程B执行reader()方法,那么A线程执行之后的共享变量的状态示意图如下:

结论:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

结论:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

由此可以得出结论:volatile的内存可见性是如果一个线程修改了共享变量,那么该共享变量会立刻刷新到主存中。同时,会通知另外一个持有该共享变量的线程,告诉它这个共享变量已经修改了,不要再使用你工作内存中的变量值了,快去主内存中重新获取吧。

总的来说:volatile使用了内存屏障来禁止指令的重排序,使用刷新主内存,通知其他线程工作内存中的共享变量失效,使其他线程强制去主内存获取最新的值来保证,被volatile修饰的变量的内存可见性。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。