Java并发(四):volatile的内存语义

文章探讨了Java中volatile关键字的作用,特别是在多线程环境下的变量同步和可见性问题。重点解释了volatile的内存语义,如写入的内存刷新和读取的无效化,以及它如何确保线程间通信的原子性。同时提到了volatile如何防止命令重排序,以及其在并发编程和面试题中的重要性。
摘要由CSDN通过智能技术生成

在这里插入图片描述

我这里开了两个线程,后面的线程去修改volatile变量,前面的线程不断获取volatile变量,

结果是会一致卡在死循环,控制台没有任何输出

假如将flag让volatile来进行修饰

在这里插入图片描述

结果是:三秒后,就不会不断打印出信息出来

注意,Thread.sleep是会刷新线程内存的,所以不要使用Thread.sleep来分别让一个线程获取两次volatile变量

volatile的特性


volatile其实相当于对变量的单词读或写操作加了锁、做了同步

由于是加了锁,所以就有前面提到的锁的语义,即锁的happens-before,锁的happens-before规定了释放锁的操作对于后续获得锁操作是可见的,所以释放锁的线程对于后续获得锁的线程是可见的,意味着volatile修饰的变量的最后写入是可以被后面获得锁的线程读取的

32位的操作系统去操作64位的变量时,会分成高32位和低32位去执行,但由于锁,会导致这个操作也是具有原子性的,因为锁的语义决定了临界区代码的执行具有原子性,即必须要整个代码块执行完,如果没有锁,那么就不是原子性的,可能会被分成不连续的两步来执行

所以,volatile变量自身是具有下面特性的

  1. 原子性:无论多大的变量,对其单词读或写操作都是具有原子性的,但如果类似于i++这种操作就不具备原子性了,因为这本来就是两条命令

  2. 可见性:操作volatile变量的线程是可以获取前一个线程对其的修改,即当前线程总是可以看到volatile变量最后的写入

volatile 写与读的内存语义


我们先来研究一下什么依赖关系需要volatile

前面提到过总共有三种依赖关系

  • 读后写

  • 写后读

  • 写后写

volatile是实现可见性的,所以写后写就不用考虑了,而且读后写是不需要可见性的,所以需要可见性的是写后读

写语义

volatile写的内存语义如下:

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存(即不仅修改了本地内存,而且还刷新到了主内存),注意,这个刷新是按缓存行的形式(64字节)

举个栗子

两个线程,A线程修改flag与A,flag与A原本为默认值

在这里插入图片描述

所以volatile的写是有两个操作的,然后这两个操作会合成一个原子操作

读语义

volatile的读内存语义为:当读一个volatile变量时,JVM会把线程对应的本地内存置为无效,接下来重新去主内存中读取共享变量,并且更新本地内存,注意:是读的时候会置为无效,假如不读就不会置为无效然后重新获取

还是上面的栗子,不过多了一个线程B,线程B一开始读的是默认值,后来再进行了一次读取

在这里插入图片描述

总结一下读写语义

读写语义对应的其实就是volatile的变量修饰后,会进行怎样的过程

其实volatile的读写语义,就是线程之间的通信,所以volatile也是实现了线程之间的通信,来提供可见性

  • 线程A去写volatile变量,实质上是线程A对其他要操控该volatile变量的其他线程发出了消息,该消息表明了线程A已经把该变量修改了,其他线程需要重新去获取

  • 线程B去读volatile变量时,实质上是线程B接收到了之前某个线程发出的消息(可能没有消息,不过也认为接收到),知道这个变量改了,需要去重新获取

  • 所以A写B读,就实现了两个线程之间的通信,虽然不太严谨,因为可能A不写,B也要读

volatile的实现


前面已经提到过volatile的实现,字节码上加了acc_volatile修饰符,然后指令层面上是使用了内存屏障,下面就来再详细研究

volatile的内存语义实现

volatile还有一个功能就是可以防止命令重排序,也就是volatile的内存语义

为了实现volatile内存语义,JMM会限制重排序,因为重排序会让语义出现变化,也就是会打断与别的线程的通信,前面提到过,重排序总共有三种,而JMM会限制编译器重排序与处理器重排序,并不会限制内存重排序

规则如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m1oJysd4-1623666295794)(C:\Users\111\AppData\Roaming\Typora\typora-user-images\image-20210614163848771.png)]

单纯看表,很难去辨别为什么,所以下面只看不发生重排序的部分

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(一)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(二)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Redis常见面试题汇总(300+题)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

vG-1714133608186)]

Mysql面试题汇总(一)

[外链图片转存中…(img-fwQ4Ldgi-1714133608187)]

Mysql面试题汇总(二)

[外链图片转存中…(img-DoVQtaV7-1714133608187)]

Redis常见面试题汇总(300+题)

[外链图片转存中…(img-9cwAsjiD-1714133608187)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 28
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值