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. Leetcode:96. 不同的二叉搜索树

    Leetcode:96. 不同的二叉搜索树 Leetcode:96. 不同的二叉搜索树 题目在链接中,点进去看看吧! 先介绍一个名词:卡特兰数 卡特兰数 卡特兰数Cn满足以下递推关系: \[ C_{n ...

  2. phpstrom laravel代码自动提示

    1.安装composer包 composer require barryvdh/laravel-ide-helper dev-master 2.目录:\config\app.php 的'provide ...

  3. js this是什么?[多次书写]

    前言 以前的时候,我写了一个关于js this的博客,写的非常复杂,分析了各种情况. 现在我想简化. 如果你有后台基础,专门去理解过this,那么请忘记. 这东西是有口诀的: 在方法中,this 表示 ...

  4. Uncaught TypeError: Cannot set property 'onclick' of null解决办法

    如果把js内容直接放在这个head标签以内,button按钮不能正常点击更换body的背景颜色,报错提示:demo6.html:16 Uncaught TypeError: Cannot set pr ...

  5. 解读前端js中签名算法伪造H5游戏加分

    信息安全在我们日常开发中息息相关,稍有忽视则容易产生安全事故.对安全测试也提出更高要求.以下是笔者亲自实践过程: 一. 打开某个数钱游戏HTML5页面,在浏览器 F12 开发工具中,查看的js,如下, ...

  6. hadoop3自学入门笔记(3)-java 操作hdfs

    1.core-site.xml <configuration> <property> <name>fs.defaultFS</name> <val ...

  7. 常量, char[], const char[], char*, const char*, char* const以及const char* const的详解

    注意,这里用char类型只是举了一个例子,其他的int之类的也通用. 1: 常量: 例子: char str[] = "Hello world!"; char ch = 'a'; ...

  8. Foxmail for windows 客户端设置和 IMAP、POP3/SMTP 的设置

    Foxmail支持微信扫码.手机验证码.账号密码三种方式新建腾讯企业邮箱. 注意:目前仅foxmail 7.2.11版本支持微信扫码和手机验证码新建腾讯企业邮箱,可以foxmail官网https:// ...

  9. Play! 1.x Eclipse Debug调试报错解决方法记录

    使用Play eclipsify xxxx[项目路径],可以把play new xxxx[项目路径]创建的工程生成为Eclipse的项目 但是在Debug AS 调试的时候,会报以下错误 Error ...

  10. [转]Android Adapter以及getView()方法的理解

    Android Adapter基本理解: 我的理解是: 1.一个有许多getter的类(就是getView(),getCount()....这些方法) 2.有多少个get方法?都是什么? 这些gett ...