在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurren包等。在前一篇文章中,我们也介绍了synchronized的用法及原理。本文,来分析一下另外一个关键字——volatile。
  
  本文就围绕volatile展开,主要介绍volatile的用法、volatile的原理,以及volatile是如何提供可见性和有序性保障的等。
  
  volatile这个关键字,不仅仅在Java语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。尤其在C语言、C++以及Java中,都有volatile关键字。都可以用来声明变量或者对象。下面简单来介绍一下Java语言中的volatile关键字。
  
  volatile的用法
  
  volatile通常被比喻成"轻量级的synchronized",也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
  
  volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了。
  
  public class Singleton {
  
  private volatile static Singleton singleton;
  
  private Singleton (){}
  
  public static Singleton getSingleton() {
  
  if (singleton == null) {
  
  synchronized (Singleton.class) {
  
  if (singleton == null) {
  
  singleton = new Singleton();
  
  如以上代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用volatile关键字修饰可能被多个线程同时访问到的singleton。
  
  volatile的原理
  
  在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存,就存在缓存数据不一致问题。
  
  但是,对于volatile变量,当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中。
  
  但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议
  
  缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
  
  所以,如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。
  
  volatile与可见性
  
  可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  
  我们在再有人问你Java内存模型是什么,就把这篇文章发给他中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
  
  前面的关于volatile的原理中介绍过了,Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。
  
  volatile与有序性
  
  有序性即程序执行的顺序按照代码的先后顺序执行。
  
  我们在再有人问你Java内存模型是什么,就把这篇文章发给他中分析过:除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save 有可能被优化成load->save->add 。这就是可能存在有序性问题。
  
  而volatile除了可以保证数据的可见性之外,还有一个强大的功能,那就是他可以禁止指令重排优化等。
  
  普通的变量仅仅会保证在该方法的执行过程中所依赖的赋值结果的地方都能获得正确的结果,而不能保证变量的赋值操作的顺序与程序代码中的执行顺序一致。
  
  volatile可以禁止指令重排,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被volatile修饰的变量的操作,会严格按照代码顺序执行,load->add->save 的执行顺序就是:load、add、save。
  
  volatile与原子性
  
  原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。
  
  我们在Java的并发编程中的多线程问题到底是怎么回事儿?中分析过:线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。
  
  在上一篇文章中,我们介绍synchronized的时候,提到过,为了保证原子性,需要通过字节码指令monitorenter和monitorexit,但是volatile和这两个指令之间是没有任何关系的。
  
  所以,volatile是不能保证原子性的。
  
  在以下两个场景中可以使用volatile来代替synchronized:
  
  1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程会修改变量的值。
  
  2、变量不需要与其他状态变量共同参与不变约束。
  
  除以上场景外,都需要使用其他方式来保证原子性,如synchronized或者concurrent包。
  
  我们来看一下volatile和原子性的例子:
  
  public class Test {
  
  public volatile int inc = 0;
  
  public void increase() {
  
  inc++;
  
  }
  
  public static void main(String[www.dfgj157.com ] args) {
  
  final Test test = new Test(www.thd178.com);
  
  for(int i=0;i<10;i++){
  
  new Thread(www.xinghenyule.com){
  
  public void run(www.120xh.cn) {
  
  for(int j=0;j<1000;j++)
  
  test.increase();
  
  };
  
  }.start();
  
  }
  
  while(Thread.activeCount()>1) //保证前面的线程都执行完
  
  Thread.yield();
  
  System.out.println(test.inc);
  
  以上代码比较简单,就是创建10个线程,然后分别执行1000次i++操作。正常情况下,程序的输出结果应该是10000,但是,多次执行的结果都小于10000。这其实就是volatile无法满足原子性的原因。
  
  为什么会出现这种情况呢,那就是因为虽然volatile可以保证inc在多个线程之间的可见性。但是无法inc++的原子性。
  
  总结与思考
  
  我们介绍过了volatile关键字和synchronized关键字。现在我们知道,synchronized可以保证原子性、有序性和可见性。而volatile却只能保证有序性和可见性。
  
  那么,我们再来看一下双重校验锁实现的单例,已经使用了synchronized,为什么还需要volatile?
  
  public class Singleton {
  
  private volatile static Singleton singleton;
  
  private Singleton (){}
  
  public static Singleton getSingleton() {
  
  if (singleton == null) {
  
  synchronized (Singleton.class) {
  
  if (singleton == null) {
  
  singleton = new Singleton();
  
  }
  
  }
  
  }
  
  return singleton;

volatile的用法的更多相关文章

  1. Java volatile的用法---转载

    我们知道,在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步. 这在JVM 1.2之前,Java的内存模型实现总是从主 ...

  2. C#中volatile的用法

    恐怕比较一下volatile和synchronized的不同是最容易解释清楚的.volatile是变量修饰符,而synchronized则作用于一段代码或方法:看如下三句get代码: int i1;  ...

  3. KEIL里 Volatile的用法

    volatile用于防止相关变量被优化. 例如对外部寄存器的读写.对有些外部设备的寄存器来说,读写操作可能都会引发一定硬件操作,但是如果不加volatile,编译器会把这些寄存器作为普通变量处理,例如 ...

  4. static与volatile的用法

      static 1.概述 static 声明的变量在C语言中有两方面的特征: 1).变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值.这一点是它与堆栈变量和堆变量的区别 ...

  5. java并发:线程同步机制之Volatile关键字&原子操作Atomic

    volatile关键字 volatile是一个特殊的修饰符,只有成员变量才能使用它,与Synchronized及ReentrantLock等提供的互斥相比,Synchronized保证了Synchro ...

  6. c语言,volatile

          一.意义: 该关键字的意义就是表示定义的变量值随时都会改变,必须从变量的地址处读取值,所以只有这个变量在使用过程中可能被改变(比如中断程序),就需要用这个关键字说明. )volatile, ...

  7. 再有人问你volatile是什么,把这篇文章也发给他

    在上一篇文章中,我们围绕volatile关键字做了很多阐述,主要介绍了volatile的用法.原理以及特性.在上一篇文章中,我提到过:volatile只能保证可见性和有序性,无法保证原子性.关于这部分 ...

  8. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  9. 一个具体的例子学习Java volatile关键字

    相信大多数Java程序员都学习过volatile这个关键字的用法.百度百科上对volatile的定义: volatile是一个类型修饰符(type specifier),被设计用来修饰被不同线程访问和 ...

随机推荐

  1. [uestc oj]H - 邱老师选妹子

    H - 邱老师选妹子 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submi ...

  2. Android(java)学习笔记124:利用Service在后台播放背景音乐

    1. 在android应用程序里,有一种没有UI的类(android.app.Service)——Service.简单来说,Service是一个 background process(背景程序),通过 ...

  3. DateTime与long互转

    DateTime转long: public static long GetDateLong(object time) { DateTime epoc = TimeZone.CurrentTimeZon ...

  4. SQL学习总结笔记

    SQL语句的效率不仅是sql语句的设计还有一些其他的原因比如网络 .是否有视图.是否有索引等等.这里主要描述的是我个人对于sql设计方面优化的一些见解: 首先要说明一下的是数据库SQL解析顺序: (1 ...

  5. errno的用法

    Linux中系统调用的错误都存储于 errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误. 编程时需要包含#include <errno.h>, ...

  6. NOIP2018 - 暑期博客整理

    暑假写的一些博客复习一遍.顺便再写一遍或者以现在的角度补充一点东西. 盛暑七月 初涉基环外向树dp&&bzoj1040: [ZJOI2008]骑士 比较经典的基环外向树dp.可以借鉴的 ...

  7. C#基础-循环语句

    while语句 int i = 1,sum=0; while (i <= 100) { sum += i; i++; } Console.WriteLine(sum); do···while语句 ...

  8. python 类的使用

    目录 类的继承 类的派生 类的组合 菱形继承问题 多态与多态性 dataclass的使用 类的继承 什么是继承,在生活中,子承父业,父亲和儿子就是继承的关系 在python中,父类和子类(派生类),父 ...

  9. city Engine 建模

    基本操作介绍 界面布局,文件组织 五个常见图层 常见规则,替换思想

  10. poj 3176 三角数和最大问题 dp算法

    题意:给一个三角形形状的数字,从上到下,要求数字和最大 思路 :dp dp[i+1][j]=max(dp[i+1][j],dp[i][j]+score[i+1][j]) dp[i+1][j+1]=ma ...