volatile是什么?

volatile是JVM提供的一种轻量级的同步机制,其具有三个特性。

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

保证可见性

JMM(java memory model)中文翻译为Java内存模型,是JVM下的一种规范,规定了JVM对于程序在内存中的变量应该以什么样的一样形式进行访问。

在JMM中对于同步则有以下的规定:

  • 线程解锁前,必须把共享变量的值刷新回主内存。
  • 线程加锁前,必须读取主内存的最新值到自己的工作内存中。
  • 加锁与解锁需是同一把锁。
  • 线程间的通信(传值)必须通过主内存来完成。

  根据JMM中所规定的可知道每个执行线程都拥有各自线程的工作内存。对变量的计算操作实则是对各自工作内存内的变量副本进行操作,然后刷新到主内存中,而对于其他线程并不能感知到这个动作,仍然操作各自的工作内存内的变量副本,而此时就需要一种机制来通知每个线程说共享变量已经发生变动,不能在使用各自工作内存中旧的变量副本。这个机制就是volatile。

  当某个变量被volatile修饰后,当一个线程修改了共享变量的值,其他线程能够立即感知这个修改。volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新该变量值。

  另外除了volatile关键字之外,Java还有两个关键字能实现可见性,即synchronized和final。而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个这个引用访问到“初始化了一半”对对象),那其他线程中就能看见final字段的值。这句话的意思是被final修饰的字段在构造方法是初始化完毕会立马刷新到主内存中,当构造器并没有把this传递出去但是有发生了this引用逃逸(JVM优化导致的指令重排序导致引用逃逸)其他线程就有可能拿到this的引用但是其构造函数却还没构造完毕,而被final字段的值具有可见性,那么在其他线程中就能够实时看见final字段的值。

  当越多线程共同争夺同一个变量时越容易出现变量不可见问题。

不保证原子性

  这个相对比较好理解,每个线程在对于获取被volatile修饰的字段可以确保获取最新的值,但是由于在计算中并没有锁机制来确保1个时间点只进行一段计算而导致该线程准备更新该字段的值时候该其他线程对该字段设置的值直接覆盖导致数据错乱。所以对某个字段需要有一段的计算过程仅仅靠volatile是无法确保这段计算过程具有原子性,而是应该靠锁机制来确保。

顺序 线程A 线程B
1 b=a+1
2 b=a+1
3 a=b
4 a=b

  假设a为5那么即使a被volatile修饰着,他也只能确保线程在执行b=a+1时a的值是最新的值,但是当a未来得及设置进主内存中,线程A与线程B的b=a+1将等于6。接下来将6设置进主内存中,此时就发生数据错误。经过2个线程执行a的值应该是7而不是6。因此在计算过程中应该加上锁来避免这类情况发生。

禁止指令重排

  计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序,一般分为以下三种。



  单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。

  处理器在进行重排序时必须要考虑指令之间的数据依赖性。

  多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致是无法确定的,结果无法预测。

因为指令重排将会带来的问题:

A线程指令重排导致B线程出错。

在线程A中:

context = loadContext();
inited = true;

在线程B中:

while(!inited ){ //根据线程A中对inited变量的修改决定是否使用context变量
sleep(100);
}
doSomethingwithconfig(context);

假设线程A中发生了指令重排序:

inited = true;
context = loadContext();

那么B中很可能就会拿到一个尚未初始化或尚未初始化完成的context,从而引发程序错误。

指令重排导致单例模式失效。

public class Singleton {
private static Singleton instance = null;
private Singleton() {
System.err.println("执行构造Singleton");
}
public static Singleton getInstance() {
if(instance == null) {
synchronzied(Singleton.class) {
if(instance == null) {
instance = new Singleton(); //非原子操作
}
}
}
return instance;
}
}

  instance= new Singleton(),它并不是一个原子操作,它可以抽象为下面几条JVM指令:

  memory =allocate(); //1:分配对象的内存空间

  ctorInstance(memory); //2:初始化对象

  instance =memory; //3:设置instance指向刚分配的内存地址

  上面操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM是可以针对它们进行指令的优化重排序的,经过重排序后如下:

  memory =allocate(); //1:分配对象的内存空间

  instance =memory; //3:instance指向刚分配的内存地址,此时对象还未初始化

  ctorInstance(memory); //2:初始化对象

  而此时instance已分配到实例地址即instance不为null,但是却未初始化对象也就是未执行构造方法进行初始化。此时另外一个线程进入该方法获取到未执行构造方法的对象进行使用就有可能导致程序报错。因此需要instance前修饰volatile来避免引用逃逸这类情况发生。

java - jmm之volatile特性的更多相关文章

  1. 全面理解Java内存模型(JMM)及volatile关键字(转载)

    关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...

  2. 全面理解Java内存模型(JMM)及volatile关键字(转)

    原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...

  3. 深入理解Java内存模型JMM与volatile关键字

    深入理解Java内存模型JMM与volatile关键字 多核并发缓存架构 Java内存模型 Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型来建立的,Java线程内存模型是标准化的,屏蔽 ...

  4. java内存模型-volatile

    volatile 的特性 当我们声明共享变量为 volatile 后,对这个变量的读/写将会很特别.理解 volatile 特性的一个好方法是:把对 volatile 变量的单个读/写,看成是使用同一 ...

  5. Java并发编程--Volatile详解

    摘要      Volatile是Java提供的一种弱同步机制,当一个变量被声明成volatile类型后编译器不会将该变量的操作与其他内存操作进行重排序.在某些场景下使用volatile代替锁可以减少 ...

  6. 深入理解Java内存模型 - volatile

    volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这 ...

  7. 深入理解Java内存模型——volatile

    volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会非常特别. 理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁 ...

  8. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  9. java基础系列--volatile关键字

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/7833881.html 1.volatile简述 据说,volatile是java语言中最轻 ...

随机推荐

  1. OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c0000000, 1073741824, 0) failed; error='Out of memory' (errno=12)

    使用docker 安装kafka时启动失败 查看报错日志 # docker logs --since 30m 71846a96e514 Excluding KAFKA_HOME from broker ...

  2. ES相关知识

    ElkStack介绍 对于日志来说,最常见的需求就是收集.存储.查询.展示,开源社区正好有相对应的开源项目:logstash(收集).elasticsearch(存储+搜索).kibana(展示),我 ...

  3. python 學習深淺拷貝、集合、、作用域、函數

    python 學習深淺拷貝.集合..作用域.函數 2020開年新冠肺炎流行大部分人員.工廠.單位無法復工生產,人員隔離每天外出都要戴口罩,在家隔離期間悶壞了感覺把半年的口糧都幹掉了,嚴重考察大家的資本 ...

  4. VMware使用与安装

    VMware安装 下载完Vmware -> 双击打开安装包 -> 选择下一步(如下图界面) 选择接受协议,点击下一步 选择经典进行安装.这个是默认安装,会把默认插件安装到相对应的路径 选择 ...

  5. Apache Solr JMX服务远程代码执行漏洞复现

    0x00 漏洞介绍 该漏洞源于默认配置文件solr.in.sh中的ENABLE_REMOTE_JMX_OPTS配置选项存在安全风险. Apache Solr的8.1.1和8.2.0版本的自带配置文件s ...

  6. Python123——测验1: Python基本语法元素 (第1周)程序题2总结

    一.题目 二.解析 (1)官方解析 (2)个人解析 def m1(): """ 法1:暴力破解""" s1 = input('') s2 = ...

  7. dotMemory 2019.3.1一直试用

    创建一个bat脚本, 里面写上: reg delete HKEY_CURRENT_USER\Software\JetBrains\dotMemory /freg delete HKEY_CURRENT ...

  8. 使用iframe实现导航栏在上面,下面的窗体刷新

    1.做一个导航栏,并设置跳转链接的<a>标签的name属性或id 此处演示name标签 <!-- 导航条 --> <nav id="navAjax" ...

  9. 使用 Jest 进行愉快的 JavaScript(TypeScript) 测试

    一般我们不管是做前端还是后端,为了提高代码的质量,会选择一种测试驱动开发(TDD)的办法来写代码进行单元测试.Jest 是 Facebook 团队开发的一款测试框架,为的是提高开发者的"开发 ...

  10. 本地服务器热更新 插件 live-server

    本地服务器热更新 插件 live-server 超级好用 强烈种草一波 无需安装到项目中 使用方法如下: 1.先全局安装live-server: npm i http-server -g 2.在需要热 ...