当前位置: 首页 > news >正文

java并发- volatile关键字

文章目录

    • 基础
    • 什么是volatile
      • 使用 volatile 的困惑
      • volatile对指令重排的影响\volatile与内存屏障
      • volatile与synchronized区别

基础

  • 可见性:
    是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

    在 Java 中 volatile、synchronized 和 final 实现可见性。

  • 原子性:
    原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

    在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。

  • 有序性:
     Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

什么是volatile

我们声明一个 volatile 变量 volatile int x = 0,它表达的是:告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。靠 volatile 语义来搞定可见性的。

当一个变量定义为 volatile 之后,将具备两种特性:

1.保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存。

2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

使用 volatile 的困惑

参考URL: https://time.geekbang.org/column/article/84017

例如下面的示例代码,假设线程 A 执行 writer() 方法,按照 volatile 语义,会把变量 “v=true” 写入内存;假设线程 B 执行 reader() 方法,同样按照 volatile 语义,线程 B 会从内存中读取变量 v,如果线程 B 看到 “v == true” 时,那么线程 B 看到的变量 x 是多少呢?

直觉上看,应该是 42,那实际应该是多少呢?这个要看 Java 的版本,如果在低于 1.5 版本上运行,x 可能是 42,也有可能是 0;如果在 1.5 以上的版本上运行,x 就是等于 42。

    // 以下代码来源于【参考1】class VolatileExample {int x = 0;volatile boolean v = false;public void writer() {x = 42;v = true;}public void reader() {if (v == true) { // 这里x会是多少呢? } }}}}

分析一下,为什么 1.5 以前的版本会出现 x = 0 的情况呢?我相信你一定想到了,变量 x 可能被 CPU 缓存而导致可见性问题。这个问题在 1.5 版本已经被圆满解决了。Java 内存模型在 1.5 版本对 volatile 语义进行了增强。怎么增强的呢?答案是一项 Happens-Before 规则。

Happens-Before 规则, 真正要表达的是:前面一个操作的结果对后续操作是可见的。

Happens-Before 约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守 Happens-Before 规则。

  1. 程序的顺序性规则
    这条规则是指在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。这还是比较容易理解的,比较符合单线程里面的思维:程序前面对某个变量的修改一定是对后续操作可见的。

  2. volatile 变量规则
    这条规则是指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。

这个就有点费解了,对一个 volatile 变量的写操作相对于后续对这个 volatile 变量的读操作可见,这怎么看都是禁用缓存的意思啊,貌似和 1.5 版本以前的语义没有变化啊?如果单看这个规则,的确是这样,但是如果我们关联一下规则 3,就有点不一样的感觉了。

  1. 传递性这条规则是指如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。

volatile对指令重排的影响\volatile与内存屏障

指令重排:JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序,为的是不改变程序结果条件下优化效率,注意这里不改变程序结果指的是单线程的情况下。

内存屏障:内存屏障(Memory Barrier)是一种CPU指令,意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。

在一个变量被volatile修饰后,对应的读写操作前会被插入内存平展,编译时JVM编译器遵循内存屏障的约束,运行时依靠CPU屏障指令来阻止重排。

volatile与synchronized区别

1、volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

2、volatile仅能使用在变量级别;synchronized使用在方法、和类级别、代码块。

3、volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

4、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

5、volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【深度学习】使用tensorflow实现VGG19网络
  • mysql mgr集群多主部署
  • 深度学习与机器学习的关系
  • list链表
  • 【AI学习】LangChain学习
  • ArcGIS学习(八)基于GIS平台的控规编制办法
  • 【Node.js】path 模块进行路径处理
  • UE5中的DataTable说明
  • 剪辑视频调色软件有哪些 剪辑视频软件哪个最好 剪辑视频怎么学 剪辑视频的方法和步骤 会声会影2024 会声会影视频制作教程
  • 【研究生复试】计算机软件工程人工智能研究生复试——资料整理(速记版)——JAVA
  • Sora了解资料
  • OpenHarmony下GN语法普法
  • easyx 枪声模拟器
  • h5网页和 Android APP联调,webview嵌入网页,网页中window.open打开新页面,网页只在webview中打开,没有重开一个app窗口
  • html的列表标签
  • 【知识碎片】第三方登录弹窗效果
  • angular2开源库收集
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • HTTP--网络协议分层,http历史(二)
  • 初识MongoDB分片
  • 给github项目添加CI badge
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 机器学习中为什么要做归一化normalization
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 老板让我十分钟上手nx-admin
  • 力扣(LeetCode)357
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 用简单代码看卷积组块发展
  • 云大使推广中的常见热门问题
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • 1.Ext JS 建立web开发工程
  • 阿里云API、SDK和CLI应用实践方案
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • #php的pecl工具#
  • #Spring-boot高级
  • #图像处理
  • (13)DroneCAN 适配器节点(一)
  • (Java数据结构)ArrayList
  • (js)循环条件满足时终止循环
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (阿里云万网)-域名注册购买实名流程
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (没学懂,待填坑)【动态规划】数位动态规划
  • (区间dp) (经典例题) 石子合并
  • (四)js前端开发中设计模式之工厂方法模式
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (转)JAVA中的堆栈
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET C# 使用GDAL读取FileGDB要素类
  • .net core Redis 使用有序集合实现延迟队列
  • .NET IoC 容器(三)Autofac
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .NET Project Open Day(2011.11.13)