前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大牛即将带你深入解析java虚拟机:并发设施,内存模型

大牛即将带你深入解析java虚拟机:并发设施,内存模型

作者头像
愿天堂没有BUG
发布2022-10-31 11:23:05
2650
发布2022-10-31 11:23:05
举报
文章被收录于专栏:愿天堂没有BUG(公众号同名)

内存模型

内存模型是指给定一段代码和这段代码被CPU执行的顺序,回答该执行顺序是否合法。编译器、Cache、CPU可以自由地调整、优化、修改、删除代码,只要保证最后CPU的执行顺序能被内存模型预测到即可,所以说,内存模型描述了程序的具体行为。

对于Java语言来说,内存模型还可以这样理解:在一些规则的约束下,检查代码执行顺序中的写操作能否被读操作观察到。这些规则被统称为内存模型,在这个模型下,可以确定任意程序点P能否读取到变量V的值。

happens-before内存模型

最严格的内存模型是顺序一致性内存模型(Sequential ConsistencyMemory Model),顺序一致性内存是指程序顺序和执行顺序完全一致,假设有变量v、读操作r、写操作w,那么顺序一致性内存模型还规定:

在执行顺序中,w发生在r前;

在w和r之间,没有其他写操作可以发生。

换句话说,顺序一致性内存模型要求CPU严格按照代码的顺序执行。它禁止了所有编译器优化和处理器重排序,这对于大多数应用来说都是不可接受的。

一种更为宽松的内存模型是happens-before内存模型。不同于顺序一致性内存模型完全禁止优化和重排序,happens-before内存模型只有几条合情合理的约束条件:

1)所有同步动作(加锁、解锁、读写volatile变量、线程启动、线程完成)的代码顺序与执行顺序一致,同步动作的代码顺序也叫作同步顺序。

同步动作中对于同一个monitor,解锁发生在加锁前面。

同一个volatile变量写操作发生在读操作前面。

线程启动操作是该线程的第一个操作,不能有先于它的操作发生。

当T2线程发现T1线程已经完成或者连接到T1,T1的最后一个操作要先于T2所有操作。

如果线程T1中断线程T2,那么T1中断点要先于任何确定T2被中断的线程的操作。

对变量写入默认值的操作要先于线程的第一个操作;对象初始化完成操作要先于finalize()方法的第一个操作。

2)如果a先于b发生,b先于c发生,那么可以确定a先于c发生。

3)volatile的写操作先于volatile的读操作。

只要保证上述条件成立,编译器和CPU就可以自由地对代码进行优化和重排序。代码清单6-8展示了如何使用happens-before内存模型预测执行顺序:

代码清单6-8 使用happens-before内存模型预测执行顺序

代码语言:javascript
复制
public class HappensBefore {
static int x = 0;
static int y = 42;
public static void main(String[] args) {
x = 1;
Thread t = new Thread() {
public void run() {
y = x;
System.out.println(y);
};
};
t.start();
x = y + 1;
}
}

根据happens-before的定义,x=1先于t.start()发生,t.start()先于x=y+1发生,t.start()也先于y=x发生,那么可以预测x=1一定先于x=y+1和y=x发生。但是x=y+1和y=x的先后顺序是不确定的。

Java内存模型

happens-before内存模型是Java内存模型的必要不充分条件,它提到的条件是必要的,但是还不能满足Java内存模型的要求。happens-before最致命的问题是它允许值“无中生有”,如图6-2所示。

图6-2a正常情况应该是r1==r2==0,但是也可能出现r1==r2==1。因为左边没有同步操作和volatile,所以读操作可能引发写操作,然后写操作使得每个读操作都能看到它们的值。在图6-2b中,x和y的相互依赖,编译器遇到这种情况时可能会推测y为任意值,最后出现r1==r2==42这种情况,但是对于这个凭空出现的42,显然是不对的。上面提到的这些问题与因果关系有关,Java内存模型扩展了happens-before内存模型,并规定了哪些因果关系是可以接受的,哪些是不可接受的。

Java早在2004年就拥有了一个良好定义的内存模型[1],解决了大量因多线程和并发带来的错误和难以发现的Bug,而C++直到2011年才拥有。新的Java内存模型相当于开发者和硬件系统通过HotSpot VM这个代理人签订的“契约”,该“契约”保证了在一些特定的地方,程序中的代码顺序与硬件的执行顺序一致,而这个一致性的保证通常是由前面提到的内存屏障实现的,如图6-3所示。

当volatile字段和普通字段读写混合时会需要一些内存屏障的支持,总结以上操作可以得到如表6-1所示的内容。

  • 普通读:读取非volatile的普通字段、静态字段,以及读取数组指定索引的元素。
  • 普通写:写入非volatile的普通字段、静态字段,以及写入数组指定索引的元素。
  • volatile读:和普通读一样,只是字段使用volatile修饰。
  • volatile写:和普通写一样,只是字段使用volatile修饰。
  • 进入monitor:进入synchronized区域。
  • 退出monitor:退出synchronized区域。

举个例子,假设第一个操作是普通写,第二个操作是volatile写,根据表6-1可知,两个操作之间需要一个StoreStore,这意味着普通写不能被重排序到volatile写后面,volatile写也不能被重排序到普通写前面。除了表6-1中提到的操作,Java内存模型对于final修饰的字段也有一些要求:如果存在对final字段的写操作,那么虚拟机会在后面插入StoreStore屏障,即x.finalField=val; StoreStore。

本文给大家讲解的内容是深入解析java虚拟机:并发设施,内存模型

  1. 下篇文章给大家讲解的是深入解析java虚拟机:并发设施,原子操作、ParkEvent和Parker;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 愿天堂没有BUG 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存模型
  • happens-before内存模型
  • Java内存模型
  • 本文给大家讲解的内容是深入解析java虚拟机:并发设施,内存模型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档