volatile 是什么?

java虚拟体提供的轻量级同步机制,可以保证内存可见性,不能保证原子性,禁止指令重排序

java内存模型

java memory model。java内存模型一种抽象概念或规范,通过这组规范定义了程序访问变量(实力字段,静态字段,数组元素等)的访问方式。

JVM运行程序的实体是线程,每个线程在创建是JVM都会为其创建一个工作内存或称栈空间,工作内存是每个线程私有的数据区域,而java内存模型中规定变量都是存储在主内存中,主内存是线程共享的内存区域,所有线程都可以访问,但线程对变量的操作必须在工作内存中进行,首先要将变量从主内存中拷贝到自己的工作内存中,对变量进行操作,操作完成后写会主存中,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中变量的副本,线程之间无法访问对方的工作内存,线程间的通信(值的传递)必须通过主内存来完成。

内存可见性测试

class MyData{int number = 0;public void addNumber(){this.number = 10;System.out.println("number值改为10");}
}public class TestVolatile {public static void main(String[] args) {MyData myData = new MyData();// 开启一个新的线程延时3毫秒后改变number的值new Thread(()->{try {Thread.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addNumber();System.out.println("线程"+Thread.currentThread().getName()+"修改了myData的number值");},"线程A").start();// 如果myData的number一直为0则程序死循环不结束while(myData.number == 0){}System.out.println("main线程结束,myData.number != 0 ");}
}

程序运行结果出乎意料,在线程A修改number的值后并没有正常的结束,由于while条件判断执行的很快,没有时间去主存中读取已经被线程A修改后的number值,一直在判断的是main线程工作内存中的number的副本。(可以在while中添加延时操作,这样main线程有机会重新加载主存中的值也可以结束),正常情况下JMM不能保证内存可见性,线程A修改变量number,对于main线程不是实时可见的。在number成员变量前添加 volatile 修饰 保证内存可见,程序即可正常退出。

不保证原子性测试

volatile并不能保证变量操作的原子执行,只能保证变量的修改时其他线程实时可见。(++操作不是原子的),如果想要保证原子性,可以使用synchronized、ReentrantLock或AtomicInteger

class MyData2 {volatile int number = 0;public void addNumber() {this.number++;}
}public class TestVolatileAtomic {public static void main(String[] args) {MyData2 myData = new MyData2();for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {myData.addNumber(); }}, String.valueOf(i)).start();}// 如果有多余的线程,main线程与gc共2个while (Thread.activeCount() > 2) {Thread.yield();}System.out.println(myData.number);}
}

为什么volatile可以保证内存可见?

Java内存模型也规定了工作内存与主内存之间交互的协议,定义了8种原子操作

  1. lock:将主内存的变量锁定,为一个线程所独占。
  2. unlock:将lock加的锁定解除,此时其他线程可以有机会访问此变量。
  3. read:将主内存中的变量值读到工作线程中。
  4. load:将read读取到的值保存到工作内存中的变量副本中。
  5. use:将值传递给线程的代码执行引擎。
  6. assign:将执行引擎处理返回的值重新赋值给变量副本。
  7. store:将变量副本的值存储到主内存中。
  8. write:将store存储的值写入到主内存的共享变量中。

lock、unlock是同步锁所产生的(如:synchronized、com.util.concurrent中的原子类)。

volatile 的作用就是保证【read、load、use】与【assign、store、write】这每组里面的操作都是有序的,强制,三条指令一起执行,实际意义就是,每次使用变量都从主内存中读取,每次修改完变量都立即刷新回主存中。

volatile禁止指令重排

指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

int a = 10; // 1
int b = 20; // 2
int c = a*b; // 3

1,2的执行顺序,并不影响步骤3的执行结果,程序在运行期间可能根据情况对指令进行重排序提升运行效率。

重排序带来的问题

一个经典的案例就是双重检测锁实现单例模式。

public class Singleton {private static Singleton instance = null;private Singleton() { }public static Singleton getInstance() {if(instance == null) {synchronzied(Singleton.class) {if(instance == null) {instance = new Singleton();  //非原子操作}}}return instance;}
}

由于jvm可能会对非原子操作进行重排序,这就会带来一些问题。

instance = new Singleton();

对象的new操作并不是原子性的。实际上new操作可抽象为三个操作指令

memory =allocate();
instance =memory;
ctorInstance(memory);
  1. 分配内存空间
  2. instance引用指向分配的内存空间
  3. 使用分配的内存初始化对象

如果jvm对new操作进行了指令重排可能会是这样

memory =allocate();
instance =memory;
ctorInstance(memory)
  1. 分配内存空间
  2. instance引用指向分配的内存空间
  3. 初始化对象

这就会带了一个问题,在多线程中,线程A第一次获取单实例执行new Singleton操作,instace为null,在执行memory =allocate();instance =memory;操作后发生线程的切换,线程B获取单实例,但这时instance引用指向了分配的内存已经不为空,但内存的初始化工作还没有执行,这就让线程B拿到了一个未初始化完成的对象,给instance添加volatile修饰禁止指令重排问题解决。

总结

volatile的关键作用

  1. 保证多线程环境中,变量在线程之间的实时可见(在AQS,原子变量中都有使用)。
  2. 防止指令重排序。

切记 volatile无法保证原子性
交流Q群 892480622

Java成神之路——volatile是什么?相关推荐

  1. Java成神之路技术整理

    转载自 Java成神之路技术整理 以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新. Java 基础篇 Java 集合篇 Java 多线程篇 J ...

  2. Java成神之路[转]

    阿里大牛珍藏架构资料,点击链接免费获取 针对本文,博主最近在写<成神之路系列文章> ,分章分节介绍所有知识点.欢迎关注. 主要版本 更新时间 备注 v1.0 2015-08-01 首次发布 ...

  3. Alibaba技术专家倾心五年打造 Java成神之路:基础篇

    近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是Java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...

  4. Java成神之路技术整理,本文长期更新!

    原文地址:https://mp.weixin.qq.com/s/N507Cfb_mbkGvHtg_FIaVg(来源:java技术栈微信公众号) 以下是Java技术栈微信公众号发布的所有关于 Java ...

  5. 【学海无涯】Java成神之路

    基础篇 面向对象 面向对象与面向过程   面向过程就是按照程序进行的顺序依次编写索要完成相应任务的方法,依次调用.面型对象注重对逻辑概念的封装,将若干变量和方法封装成类,各个对象互相调用.面向对象占用 ...

  6. Java成神之路——ASM,Javassist,cglib区别。

    class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...

  7. Java成神之路——重入锁、公平非公平锁、自旋锁、读写锁

    你知道的Java锁有哪些? synchronized?Lock?它们又有什么区别?锁可分为哪些种类?锁是如何实现的? 公平与非公平锁 公平锁与非公平锁的区别体现在锁造成阻塞时的排队机制,公平锁按申请锁 ...

  8. Java成神之路-进阶步骤(转)

    https://www.hollischuang.com/archives/3280 一.基础篇 面向对象 什么是面向对象 面向对象.面向过程 面向对象的三大基本特征和五大基本原则 平台无关性 Jav ...

  9. 夜光:Java成神之路(四)擅长的语言

    夜光序言: 女孩问:"你喜欢我吗?" "不喜欢"男孩不假思索的回答 "哦"女孩失落地低下头,眼泪快要掉下来 "傻瓜,我不喜欢你,我 ...

最新文章

  1. 90. Leetcode 剑指 Offer 62. 圆圈中最后剩下的数字 (动态规划-基础题)
  2. python实验收获_python实验课代码心得
  3. python执行文件函数,python如何运行函数
  4. php枚举属于什么类型,python枚举类型是什么?python枚举类型的简单介绍
  5. LAMP默认安装路径
  6. asp.net ajax 源码,asp.net+jquery+ajax简单留言板 v1.2
  7. 站在巨人的肩膀上学习Android开发
  8. 定点补码加减法运算_定点整数的加减法
  9. 【小程序源码】头像组合多种分类并支持姓氏头像制作生成
  10. 分享27个谷歌(Google)镜像
  11. Servlet思维导图总结
  12. [目录]-博客笔记导读目录(全部)
  13. Excel根据出生日期判断生肖,Leo老师来教你!
  14. ImportError: cannot import name ‘get_all_providers‘ from ‘onnxruntime.capi._pybind_state‘
  15. Python实现经纬度转换
  16. 全国各地疫苗接种数据进展
  17. 杭州bgp高防服务器稳定性和安全性怎么样?103.219.30.*
  18. Oracle 10.2 概念 第一章 Oracle介绍
  19. 如何做一个基于微信电影播放小程序系统毕业设计毕设作品
  20. chosen 下拉框

热门文章

  1. 百度推出挖掘机自动驾驶技术:操作不输蓝翔
  2. 关于灵魂安放,年轻人如何选择适合自己的城市呢?学长有话说
  3. java dom读写xml文件_java通过dom读写xml文件
  4. vue tree组件_Ant-Design-Vue和Icon按需加载方案 - JeecgBoot实战
  5. 一些php+mysql的开源网上商城
  6. 东方电子全资子公司中标1.065亿元国家电网第三批采购项目
  7. 华邦电子2022年1月营收为新台币86.90亿元
  8. 自如CEO熊林接任董事长
  9. realme GT大师版核心参数曝光:同样一亿像素主摄
  10. 微软宣布明年停止支持已推出25年的IE浏览器