关于 Java 关键字 volatile 的总结

  1 什么是 volatile

  volatile 是 Java 的一个关键字,它提供了一种轻量级的同步机制。相比于重量级锁 synchronized,volatile 更为轻量级,因为它不会引起线程上下文的切换和调度。

  2 volatile 的两个作用

  可以禁止指令的重排序优化

  提供多线程访问共享变量的内存可见性

  3 禁止指令重排

  3.1 什么是指令重排

  指令重排序是 JVM 为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度,例如将多条指令并行执行或者是调整指令的执行顺序。但是在多线程的情况下,指令重排序可能会带来问题,例如程序执行的顺序可能会被调整。在加上 volatile 关键字之后可以有效解决这个问题。

  下面我们举个例子:

  double r = 2.1; //(1)

  double pi = 3.14;//(2)

  double area = pi*r*r;//(3)

  在代码语句的顺序为 1->2->3,但实际上顺序无论是 1->2->3 还是 2->1->3 对结果并无影响,所以在编译时和运行时可以根据需要对1、2语句进行重排序。

  重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:

  1 不会对存在数据依赖关系的操作进行重排序

  2 重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变

  3.2 指令重排带来的问题

  我们来看看这个基于双重检验的单例模式:

  public class Singleton3 {

  private static Singleton3 instance = null;

  private Singleton3() {}

  public static Singleton3 getInstance() {

  if (instance == null) {

  synchronized(Singleton3.class) {

  if (instance == null)

  instance = new Singleton3();// 非原子操作

  }

  }

  return instance;

  }

  }

  事实上,这个单例模式的实现方式是有问题的,问题在哪呢?问题在于instance = new Singleton3(); 并不是一个原子操作。

  我们可以将其抽象成以下几条指令:

  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 指向分配好的内存放在了前面,而这段内存的初始化被排在了后面。在线程A执行这段赋值语句,在初始化分配对象之前就已经将其赋值给 instance 引用,恰好另一个线程进入方法判断 instance 引用不为 null,然后就将其返回使用,导致出错。

  3.3 禁止指令重排的原理

  volatile 关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

  内存屏障会确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。郑州哪家医院看妇科好 http://www.120zzkd.com/

  对于上面的基于双重检验的单例模式,我们只需对其稍作修改即可令其正确运行。我们已经知道,问题来自于指令重排,那么我们禁止指令重排即可,用 volatile 关键字修饰 instance 变量,使得 instance 在读、写操作前后都会插入内存屏障,避免重排序。完整代码如下:

  public class Singleton3 {

  private static volatile Singleton3 instance = null;

  private Singleton3() {}

  public static Singleton3 getInstance() {

  if (instance == null) {

  synchronized(Singleton3.class) {

  if (instance == null)

  instance = new Singleton3();

  }

  }

  return instance;

  }

  }

  4 保证内存可见性

  4.1 什么是保证内存可见性

  Java 支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象以及成员变量分配的内存是在共享内存中的,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是加速程序的执行,这是现代多核处理器的一个显著特性),所以程序在执行过程中,一个线程看到的变量并不一定是最新的。volatile 告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

  4.2 实现的具体细节

  如果对声明了 volatile 的变量进行写操作,JVM 就会向处理器发送一条 Lock 前缀的指令,将这个变量所在缓存行的数据写回到系统内存。

  但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

  具体的说,内存可见性也是通过内存屏障实现的,它会执行下面两个操作:

  强制将对缓存的修改操作立即写入主存

  如果是写操作,它会导致其他 CPU 中对应的缓存行无效

  5 总结

  volatile 提供了一种轻量级的同步机制,在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞

  volatile 只能确保可见性,而加锁机制既可以确保可见性又可以确保原子性

  volatile 屏蔽掉了 JVM 中必要的代码优化,所以在效率上比较低

  相比 synchronized,虽然 volatile 更简单并且开销更低,但它的同步性较差,而且其使用也更容易出错

原文地址:https://www.cnblogs.com/djw12333/p/12028921.html

时间: 2024-10-13 17:16:04

关于 Java 关键字 volatile 的总结的相关文章

Java关键字---volatile

一.计算机中线程不安全问题产生原因 计算机在执行程序时,每条指令都是在CPU中执行的,执行的过程会涉及到读取和写入.程序运行过程中的临时数据是存放在主存(物理内存)中的,这就会产生一个问题,由于CPU的执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU的执行速度相比就慢很多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,就会大大降低指令的执行速度. 为了解决这个问题,计算机中就有了CPU缓存的概念.在程序的运行过程中,操作的数据会从内存中复制一份到CPU缓存中,当CPU进行计

java关键字 volatile的作用及使用说明

先来看看这个关键字是什么意思:volatile  [?v?l?ta?l] adj. 易变的,不稳定的; 从翻译上来看,volatile表示这个关键字是极易发生改变的.volatile是java语言中,最轻量级的并发同步机制.这个关键字有如下两个作用:1.任何对volatile变量的修改,java中的其他线程都可以感知到2.volatile会禁止指令冲排序优化 在详细讲解volatile关键字之前,需要对java的内存模型有所理解,否则很难深入的认识到volatile的作用.java 内存可以像之

java 关键字volatile

一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来).线程对变量的所有操作(读取,赋值)都必须在工作内存中进行.不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成. 基于此种内存模型,便产生了多线程编程中的数据“脏读”等问题. 举个简单的例子:在j

转:java中volatile关键字的含义

转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制. synchronized 同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用 sync

java中volatile关键字的含义

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制. synchronized 同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法 或者 代码块.

Java 并发 关键字volatile

Java 并发 关键字volatile @author ixenos volatile只是保证了共享变量的可见性,不保证同步操作的原子性 同步块 和 volatile 关键字机制 synchronized  同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用synchronized 修饰的方法 或者 代码块. volatile 用volatile修饰的变量,线程在每次刚使用变量的时候,

Java关键字transient和volatile小结(转)

Java关键字transient和volatile小结(转) transient和volatile两个关键字一个用于对象序列化,一个用于线程同步,都是Java中比较高阶的话题,简单总结一下. transient transient是类型修饰符,只能用来修饰字段.在对象序列化的过程中,标记为transient的变量不会被序列化. 示例: class Test { transient int a; // 不会被持久化 int b; // 持久化 } 当类Test的实例对象被序列化(比如将Test类的

java中volatile关键字的含义(转载)

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制. synchronized 同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法 或者 代码块.

java中volatile关键字的含义(转载自http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html)

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制. synchronized 同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法 或者 代码块.