前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java内存模型(可见性有序性)

Java内存模型(可见性有序性)

作者头像
shysh95
发布2021-04-02 08:31:44
4070
发布2021-04-02 08:31:44
举报
文章被收录于专栏:shysh95shysh95

摘要

本文的主题是Java内存模型的可见性,主要解决以下几个问题:

  • 什么是可见性
  • 什么是有序性
  • 指令重排序
  • 如何保证线程间有序性
  • 先行发生原则
  • volatile关键字
  • synchronized关键字

1. 概念

1.1 什么可见性

可见性是指当一个线程修改了共享变量的值以后,其他线程可以立即得知这个修改。

1.2 什么是有序性

Java中的有序性在不加干预的情况下可以总结为:在线程中观察自身的操作是有序的(线程内表现为串行语义),在一个线程观察另一个线程所有的操作都是无序的(指令重排序和工作内存与主内存同步延迟)。线程之间的有序性需要我们通过一些措施来保证。

2. 指令重排序

指令重排序是CPU在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待,只需要保证最后能得出正确的结果。通过乱序执行的技术,处理器可以大大提高执行效率。

Java虚拟机的JIT即时编译器也会采用指令重排序的技术,即生成的机器指令与字节码指令顺序不一致。

3. 如何保证有序性

由于指令重排序和内存同步延迟的问题,Java中提供了volatile和synchronized关键字来保证线程间的有序性。这两个关键字我们放在后面单独讲(毕竟可见性、有序性都和他们有关)。

除了上述两个关键字,Java语言中有一个先行发生原则,这个原则是判断数据是否存在竞争,线程是否安全的主要依据。

先行原则是Java内存模型中定义两项操作的偏序关系,如果A操作先行发生于操作B,那么操作A产生的影响要能被操作B观察到,比如修改了内存变量的共享值。

3.1 先行发生原则

如果两个操作不在先行发生原则中,并且无法推导出来的话,这两个操作就没有顺序性保障,虚拟机可以对他们进行任意排序。

先行发生原则如下:

  • 程序次序原则:同一个线程内,按照控制流顺序(分支、循环等),书写在前面的操作先行发生于书写在后面的操作
  • 管程锁定原则:一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则:对于一个volatile变量的写操作先行发生于后面对这个变量的读操作(这和可见性有关系)
  • 线程启动规则:Thread对象的start方法先行发生于此线程的每一个动作
  • 线程终止原则:线程中的所有操作先行发生于对此线程的终止检测
  • 线程中断原则:对线程interrupt方法的调用先行发生于被中断线程的代码检测到中断事件的发生(Thread.interrupted()方法可以检测到是否有中断发生)
  • 对象终结原则:一个对象的初始化完成先行发生于它的finalized()方法的开始
  • 传递性:如果操作A先行发生于B,操作B先行发生于C,那么操作A肯定先行发生于C

4. volatile关键字

volatile关键字在可见性和有序性有着关键性作用,但读者一定要记住一点:volatile关键字修饰的变量并不一定是线程安全的,他只保证变量被一个线程修改了以后,另一个线程可以立即观察到。

volatile关键字之所以能保证可见性、有序性,是因为Java内存模型对volatile修饰的变量使用有着以下规则:

  • 对volatile变量的use操作前必须要有load操作,对volatile变量的load操作后面必须有use操作,也就是说load、use操作必须连续,这就保证了volatile变量在使用前必须重新从主内存刷新到工作内存中
  • 对volatile变量的assign操作后面必须有store操作,对voldatile变量的store操作前面必须有assign操作,也就是说assign、store必须连续,这就保证了volatile变量的值在修改以后必须从工作内存刷新到主内存中

上述两条规则保证了volatile变量的可见性。

  • 假设操作A是线程T对volatile变量的use或者assign,假定操作B是和操作A相关联的load或者store,假定操作C是操作B相关联的read或者write;类似的操作D是线程P对另一个volatile变量的use或者assign,假定操作E是和操作D相关联的load或者store,假定操作F是和操作E相关联的read或者write,如果A先于D,那么C必定先于F

上述规则确保了volatile变量不会被指令重排序优化。

5. synchronized

synchronized是Java开发最熟悉不过的用来保证线程安全的关键字,上面我们提到lock和unlock是两个原子性指令,但是这两个指令并未开放给用户使用,而是提供monitorenter和monitorexit字节码指令来隐式的使用这两个操作,这两个操作反映到Java语言层面便是synchronized关键字。synchronized块中的操作也具备原子性。

synchronized关键字也可以保证变量可见性,原因是:对一个变量的unlock操作之前,必须把此变量同步回主内存中(store和write操作)。

synchronized关键也可以保证有序性,原因是:一个变量在同一时刻只允许一条线程对其执行lock操作,这条规则决定了持有同一个锁的两个同步块只能串行执行。

本期的Java内存模型可见性-有序性介绍到这,我是shysh95,顺手关注+在看,我们下期再见!!!

往期推荐

Java内存模型

Java学习路线

JIT即时编译器(C1和C2)

JIT即时编译(基础概念)

Java泛型擦除

Java编译原理(javac)

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

本文分享自 程序员修炼笔记 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档