深入了解Java中Volatile关键字

一、基本概念

先补充一下概念:Java 内存模型中的可见性、原子性和有序性。

可见性:

  可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

  可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

  在 Java 中 volatile、synchronized 和 final 实现可见性。

原子性:

  原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

  在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。

有序性:

  Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

下面内容摘录自《Java Concurrency in Practice》:

  下面一段代码在多线程环境下,将存在问题。

+ View code
 /**
  * @author zhengbinMac
  */
  public class NoVisibility {
   private static boolean ready;
   private static int number;
   private static class ReaderThread extends Thread {
     @Override
     public void run() {
       while(!ready) {
         Thread.yield();
       }
       System.out.println(number);
     }
   }
   public static void main(String[] args) {
     new ReaderThread().start();
     number = 42;
     ready = true;
   }
 }

NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值。甚至NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为“重排序”。只要在某个线程中无法检测到重排序情况(即使在其他线程中可以明显地看到该线程中的重排序),那么就无法确保线程中的操作将按照程序中指定的顺序来执行。当主线程首先写入number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写入的顺序完全相反。

  在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行春旭进行判断,无法得到正确的结论。

  这个看上去像是一个失败的设计,但却能使JVM充分地利用现代多核处理器的强大性能。例如,在缺少同步的情况下,Java内存模型允许编译器对操作顺序进行重排序,并将数值缓存在寄存器中。此外,它还允许CPU对操作顺序进行重排序,并将数值缓存在处理器特定的缓存中。

二、Volatile原理

  Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

  当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。

  而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

当一个变量定义为 volatile 之后,将具备两种特性:

  1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

  2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile 性能:

  volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

以上就是深入了解Java中Volatile关键字的详细内容,更多关于Java中Volatile关键字的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java多线程 volatile关键字详解

    volatile volatile是一种轻量同步机制.请看例子 MyThread25类 public class MyThread25 extends Thread{ private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } public vo

  • java volatile关键字使用方法及注意事项

    java volatile关键字使用方法及注意事项 什么是volatile关键字 volatile 关键字在多线程程序中起着很重要的作用.当多个线程操作同一个变量时,每个线程将拥有对那个变量的本地缓存拷贝,因此,当某一个线程修改了这个变量的值时,实际上修改的是它本地缓存中的变量值,而不是主内存中的变量值,操作这个变量的其他线程并不知道这个变量的值被改变了.为了避免这种情况,我们可以用 valatile 关键字声明这个变量,用 valatile 声明了这个变量之后,变量将不在本地缓存中保存,而在主

  • Java Volatile关键字实现原理过程解析

    volatile的用法 volatile通常被比喻成"轻量级的synchronized",也是Java并发编程中比较重要的一个关键字.和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量.无法修饰方法及代码块等. volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了. 如以下代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用volatile关键字修饰可能被多个线程同时访问到的single

  • Java面试官最喜欢问的关键字之volatile详解

    前言 笔者去年面试过几家公司,基本上每家公司都会问到volatile,甚至有的公司每轮面试的时候都会问到.面试官这么喜欢问volatile就是因为这个关键字涉及到的知识点较多比如Java内存模型.内存屏障.happen-befor等知识,可以继续挖掘到系统指令.超线程等知识. Java内存模型(JMM) volatile是Java虚拟机提供的最轻量的同步机制,但很难被正确的理解与使用,通过学习Java内存模型对volatile专门定义的一些特殊访问规则,或许会对理解volatile有一定帮助.

  • Java Volatile关键字同步机制详解

    Volatile关键字--最轻量级的同步机制1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的.(实现可见性) 例如:如果一个oldvalue -->修改为newvalue ,这时的newvalue可以被其他的线程看到. 2.volatile不是线程安全的,只能保证对单次读/写的原子性.i++ 这种操作不能保证原子性.(不能保证原子性)最常使用场景:一写多读代码演示Volatile的可见性 public class VolatileCa

  • Java并发教程之volatile关键字详解

    引言 说到多线程,我觉得我们最重要的是要理解一个临界区概念. 举个例子,一个班上1个女孩子(临界区),49个男孩子(线程),男孩子的目标就是这一个女孩子,就是会有竞争关系(线程安全问题).推广到实际场景,例如对一个数相加或者相减等等情形,因为操作对象就只有一个,在多线程环境下,就会产生线程安全问题.理解临界区概念,我们对多线程问题可以有一个好意识. Jav内存模型(JMM) 谈到多线程就应该了解一下Java内存模型(JMM)的抽象示意图.下图: 线程A和线程B执行的是时候,会去读取共享变量(临界

  • Java中Volatile关键字详解及代码示例

    一.基本概念 先补充一下概念:Java内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰的

  • 一文精通Java中的volatile关键字

    前言 在一些开源的框架的源码当中时不时都可以看到volatile这个关键字,最近特意学习一下volatile关键字的使用方法. volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见. 相较于 synchronized 是一种较为轻量级的同步策略. 缺点: 1. volatile 不具备"互斥性" 2. volatile 不能保证变量的"原子性" 很多资料中是这样介绍volatile关键字的: volatile是轻量级的synchroniz

  • Java中volatile关键字的作用与用法详解

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile 关键字作用是,使系统中所有线程对该关键字修饰的变量共享可见,可以禁止线程的工作内存对volatile修饰的变量进行缓存. volatile 2个使用场景: 1.可见性:Java提供了volatile关键字来保证可见性. 当一个共享变量被volatile修饰时,它会保证修

  • 谈谈Java中Volatile关键字的理解

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机.volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情. 一.前言 JMM提供了volatile变量定义.final.synchronized块来保证可见性. 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值.volatile很容

  • 深入了解Java中Volatile关键字

    一.基本概念 先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰

  • java中volatile关键字的作用与实例代码

    一,什么是volatile关键字,作用是什么 volatile是java虚拟机提供的轻量级同步机制 ​ 作用是: 1.保证可见性 2.禁止指令重排 3.不保证原子性 本篇具体就讲解 什么叫保证了可见性, 什么叫禁止指令重排,什么是原子性 而在这之前需要对JMM 有所了解 二,什么是JMM ​ JMM(java 内存模型 Java Memory Model 简称JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量(实例字段,静态字段和

  • Java中volatile关键字的作用

    目录 一.volatile作用 二.什么是可见性 三.什么是总线锁和缓存锁 四.什么是指令重排序 一.volatile作用 可以保证多线程环境下共享变量的可见性 通过增加内存屏障防止多个指令之间的重排序 二.什么是可见性 可见性是指当一个线程对于共享变量的修改,其他线程可以立刻看到修改之后的一个值,可见性本质上由几个方面造成的 cpu层面的高速缓存,在cpu里面呢设计了三级缓存去解决cpu运算效率和内存IO效率的问题,但是它也带来缓存一致性的一个问题,而在多线程执行的情况下呢,缓存一致性的问题就

  • Java中Volatile关键字能保证原子性吗

    目录 volatile 有序性 可见性 是否能够保证原子性 volatile volatile 是 Java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量. 而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可见性. 那么什么是有序性,什么又是可见性呢? 有序性 那么什么是有序性呢? 其实程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序. 看似理所当然,其实并不是这样,指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结

  • 解析java中volatile关键字

    在java多线程编程中经常volatile,有时候这个关键字和synchronized 或者lock经常有人混淆,具体解析如下: 在多线程的环境中会存在成员变量可见性问题: java的每个线程都存在一个线程栈的内存空间,该内存空间保存了该线程运行时的变量信息,当线程访问某一个变量值的时候首先会根据这个变量的地址找到对象的堆内存或者是栈堆存(原生数据类型)中的具体的内容,然后把这个内同赋值一个副本保存在本线程的线程栈中,紧接着对这个变量的一切操作在线程完成退出之前都和堆栈内存中的变量内容是没有关系

  • 深入解析Java中volatile关键字的作用

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

  • Java中volatile关键字实现原理

    前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解volatile的原理,从而更好.更正确地地使用volatile关键字. CPU缓存 CPU缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为C

随机推荐