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

前言

笔者去年面试过几家公司,基本上每家公司都会问到volatile,甚至有的公司每轮面试的时候都会问到。面试官这么喜欢问volatile就是因为这个关键字涉及到的知识点较多比如Java内存模型、内存屏障、happen-befor等知识,可以继续挖掘到系统指令、超线程等知识。

Java内存模型(JMM)

volatile是Java虚拟机提供的最轻量的同步机制,但很难被正确的理解与使用,通过学习Java内存模型对volatile专门定义的一些特殊访问规则,或许会对理解volatile有一定帮助。

Java内存模型定义了线程和内存之间关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读 / 写共享变量的副本。本地内存是 JMM 的一个抽象概念,并不真实存在;它涵盖内存、缓存、寄存器以及其他的硬件和编译器优化。Java的内存模型抽象如下:

volatile的语义

volatile主要提供了两种语义:

1,可见性:

可见性是指一个线程写入的值,其他线程能够立即读取。在由Java内存模型可知道,每个线程都是有本地内存。所以线程A写入在正常情况下,线程B不能立即读取。但是在volatile变量,可以保证线程A不写入本地内存直接写入主内存,线程B直接从主内存中读取,不从本地内存中读取。

2,禁止指令重排序:

重排序是指编译器和处理器为了优化程序性能而对指令进行重排序的一种优化手段。

Java程序的几种重排序

  • 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  • 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行

volatile的技术基石--内存屏障

内存屏障是cpu指令,该指令保证特定操作的顺序性和某些内存的可见性。插入一条内存屏障指令之后会告诉编译器和CPU:不管什么指令都不能和这条指令重排序。内存屏障所做的另外一件事情就是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入cache的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。

对于Java程序而言,如果把加入volatile关键字的代码和未加入volatile关键字的代码都生成汇编代码,会发现加入volatile关键字的代码会多出一个lock前缀指令。

volatile的典型用例

状态标志,代码示例如下:

线程1执行run()的过程中,可能有另外的线程2调用了shutdown,所以stop变量必须是volatile(利用的volatile的可见性)。

还有一种常见的用法在双重检验的单例实现上,代码如下:

instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:

  • 给 instance 分配内存
  • 调用 Singleton 的构造函数来初始化成员变量
  • 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

如果instance变量没有加volatile,因为指令重排序的存在,就可能导致执行步骤是1-2-3,也可能是1-3-2。一旦是1-3-2,就可能会导致访问未初始化的内存。但是加上volatile关键字之后,一定保证是按照1-2-3步骤执行的(利用的volatile的禁止重排序)。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • java volatile关键字作用及使用场景详解

    1. volatile关键字的作用:保证了变量的可见性(visibility).被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象.如以下代码片段,isShutDown被置为true后,doWork方法仍有执行.如用volatile修饰isShutDown变量,可避免此问题. public class VolatileTest3 { static class Work { boolean isShutDown = false; void shutdown(

  • Java中的关键字volatile详解

    volatile关键字经常用来修饰变量.不过,volatile本身很容易被误用.本篇就介绍一下volatile的原理和使用方式. 在介绍volatile关键字原理前,我们首先要了解JVM运行时的内存分配逻辑. 对于成员变量i,它存储在堆内存中.每个线程在运行时都会有一个自己的线程栈,线程如果要访问类的成员变量i,会通过引用获取到堆中变量i实际的值10,然后把这个变量值拷贝到自己的栈内存中,作为一个变量副本,之后线程便不再会与堆中的变量有实际联系.每个线程都有一个自己的本地副本,相互隔离.线程访问

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

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

  • 深入理解Java中的volatile关键字(总结篇)

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

  • Java多线程之volatile关键字及内存屏障实例解析

    前面一篇文章在介绍Java内存模型的三大特性(原子性.可见性.有序性)时,在可见性和有序性中都提到了volatile关键字,那这篇文章就来介绍volatile关键字的内存语义以及实现其特性的内存屏障. volatile是JVM提供的一种最轻量级的同步机制,因为Java内存模型为volatile定义特殊的访问规则,使其可以实现Java内存模型中的两大特性:可见性和有序性.正因为volatile关键字具有这两大特性,所以我们可以使用volatile关键字解决多线程中的某些同步问题. volatile

  • 详解Java面试官最爱问的volatile关键字

    本文向大家分享的主要内容是Java面试中一个常见的知识点:volatile关键字.本文详细介绍了volatile关键字的方方面面,希望大家在阅读过本文之后,能完美解决volatile关键字的相关问题.  在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性都牵扯出来,深入地话还可以考察JVM底层实现以及操作系统的相关知识. 下面我们以一次假想的面试过

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

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

  • 面试必时必问的JVM 类加载机制详解

    目录 前言 正文 1.类加载的过程. 1)加载 2)验证 3)准备 4)解析 5)初始化 2.Java 虚拟机中有哪些类加载器? 1)启动类加载器(Bootstrap ClassLoader): 2)扩展类加载器(Extension ClassLoader): 3)应用程序类加载器(Application ClassLoader): 3.什么是双亲委派模型? 4.为什么使用双亲委派模式? 5.有哪些场景破坏了双亲委派模型? 6.为什么要破坏双亲委派模型? 7.如何破坏双亲委派模型? 8.Tomc

  • 吊打Java面试官!整理了一周的Spring面试大全(附答案)

    目录 Q1:什 么 是 spring? Q2:使 用 Spring 框 架 的 好 处 是 什 么 ? Q3:使 用 Spring 缺点是什么? Q4:IoC 是什么? Q5:IOC的优点是什么 Q6:IoC 容器初始化过程? Q7:依赖注⼊的实现方法有哪些? Q8:依赖注入的相关注解? Q9:依赖注入的过程? Q10:Bean 的生命周期? Q11:Bean 的作⽤范围? Q12:如何通过 XML ⽅式创建 Bean? Q13:Spring 有几种配置方式? Q14:如何用基于 XML 配置的

  • 吊打Java面试官之Lambda表达式 Stream API

    目录 一.jdk8新特性简介 二.Lambda表达式 简单理解一下Lambda表达式 Lambda表达式的使用 三.函数式接口 1.什么是函数式接口 2.如何理解函数式接口 3.Java内置四大核心函数式接口 四.方法引用与构造器引用 方法引用 构造器引用和数组引用 五.Stream API 1.Stream API的说明 2.为什么要使用Stream API 3.创建Stream的四种方式 4.Stream的中间操作及其测试 5.Stream的终止操作及其测试 六.Optional类的使用 O

  • java方法及this关键字原理分析详解

    目录 步骤1 .给顾客增加一个吃饭的方法 步骤 2 . 没有加static的属性和方法,一定需要先new对象 步骤 3 . 用new出来的对象去执行eat方法 步骤 4 . 怎么理解c.eat() 步骤 5 . 消息接受器 步骤 6 . 如果有两个顾客? 步骤 7 . 答案 步骤 8 .其实有个this 步骤 9 . 在eat方法里面直接使用this 步骤 10 . 构造方法 步骤 11 . 总结:this的意义是什么? 步骤 12 . 道理我都懂,那static又是什么? 步骤 13 . 本节

  • java之assert关键字用法案例详解

    Java2在1.4中新增了一个关键字:assert.在程序开发过程中使用它创建一个断言(assertion).,它的语法形式有如下所示的两种形式: 1.assert condition; 这里condition是一个必须为真(true)的表达式.如果表达式的结果为true,那么断言为真,并且无任何行动 如果表达式为false,则断言失败,则会抛出一个AssertionError对象.这个AssertionError继承于Error对象, 而Error继承于Throwable,Error是和Exc

  • Java super关键字的用法详解

    目录 super关键字的三种用法: 1. super.成员变量 2. super.成员方法 3. super():调用父类中的构造方法 3.1.1 隐式调用父类无参数构造方法super() 3.1.2 显式调用父类有参数构造方法super(id) super关键字作用:在子类内部调用父类对象 基础语法: 1.在类的继承中,当子类继承了父类, 在构造子类的时候,一定要先帮助父类进行构造: 2. 调用super()必须写在子类构造方法的第一行,以保证在执行任何动作前,对象已经完成了初始化,否则编译不

  • Java this关键字的使用详解

    目录 1. 先看一段代码,并分析问题 2. 深入理解 this 3. this 的注意事项和使用细节 4. this 的案例 1. 先看一段代码,并分析问题 public class This01 { //编写一个main方法 public static void main(String[] args) { Dog dog1 = new Dog("大壮", 3); //dog1调用了 info()方法 dog1.info(); } } class Dog{ //类 String nam

随机推荐