共享模型之内存

本文详细探讨了Java内存模型中的可见性问题、有序性问题以及volatile的关键原理。通过解释JMM的工作机制,讨论了volatile如何解决可见性和部分有序性,以及synchronized和指令重排的关系。
摘要由CSDN通过智能技术生成


这一章我们进一步深入学习 共享变量多线程间的 【可见性】问题与多条指令执行时的 【有序性】问题

1. Java 内存模型

JMM 即 Java Memory Model,它定义了主存工作内存抽象概念

JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受 cpu 缓存的影响
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

2. 可见性

在这里插入图片描述
因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
在这里插入图片描述
解决方法:volatile(易变关键字):不能解决原子性,适合于只有一个写多个读的情况
它可以用来修饰成员变量静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存

synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低

volatile用在;一个线程的修改对另一个线程可见(对volatile修饰的变量可见)

重温终止模式之两阶段终止模式:
之前使用的是打断标记,选择使用停止标记
在这里插入图片描述
在这里插入图片描述

同步模式之 Balking:一个线程发现另一个线程或本线程已经做了某一件相同的事,那么本线程就无需再做了,直接结束返回
用在:当前端页面多次点击按钮调用 start 时

在这里插入图片描述

3. 有序性

指令重排:是 JIT 编译器在运行时的一些优化
口述:指令重排,其实是jit即时编译器对我们的代码做的优化,比如说我们写了两行代码,当然对于我们这个方法来说的话这两行代码谁先谁后其实互不影响的,那JIT可能就会做出优化,执行顺序上会改变,但是如果说我们的另一个方法要用到这两行代码定义的值时,在多线程环境下,就可能因为指令重排的影响进而导致出错。其实volatle就可以解决指令重排的,当然只针对一个线程中的代码来说,底层它用到了写屏障和读屏障,保证了写屏障之前的代码不会出现在写屏障后面,读屏障之后的代码不会出现在读屏障的前面,也就解决了指令重排导致的结果错误。
在这里插入图片描述
在这里插入图片描述
我们希望r1的值是1或者4,但是指令重排后可能出现0。

如何避免指令重排?
volatile 修饰的变量,可以禁用指令重排,volatile 之前的代码都不会重排。

在这里插入图片描述

3.1 volatile 原理

volatile 的底层实现原理是内存屏障,Memory Barrier

  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
在这里插入图片描述

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
在这里插入图片描述

还是那句话,不能解决指令交错:

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序

下图中,t2线程的读在t1线程的写屏障前面,对这种指令交错。volatile不能解决,这是由cpu的时间片来决定的,volatile只能解决的是一个线程中的代码不能重排,不能保证线程间的这种另一个意义上的重排。
在这里插入图片描述
总结
volatile解决了有序性和可见性,原子性不能保证。
synchronized都可以保证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值