引言

作为 Java 初学者,几乎从未使用过 volatile 关键字。但是,在面试过程中,volatile 关键字以及其作用还是经常被面试官问及。这里给各位童靴讲解一下 volatile 关键字的作用,内容涵盖 volatile 的保证内存可见性、禁止指令重排等。

1 保证内存可见性

1.1 基本概念

可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。

1.2 实现原理

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

volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量都从主内存中读,跳过CPU cache这一步。当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。

2 禁止指令重排

2.1 基本概念

指令重排序是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。指令重排序包括编译器重排序和运行时重排序。

valatile变量禁止指令重排序。针对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语句进行重排序。

2.2 指令重排带来的问题

如果一个操作不是原子的,就会给JVM留下重排的机会。

线程A中
{
context = loadContext();
inited = true;
} 线程B中
{
if (inited)
fun(context);
}

如果线程A中的指令发生了重排序,那么B中很可能就会拿到一个尚未初始化或尚未初始化完成的context,从而引发程序错误。

2.3 禁止指令重排的原理

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

JVM内存屏障插入策略:

每个volatile写操作的前面插入一个StoreStore屏障;

在每个volatile写操作的后面插入一个StoreLoad屏障;

在每个volatile读操作的后面插入一个LoadLoad屏障;

在每个volatile读操作的后面插入一个LoadStore屏障。

2.4 指令重排在双重锁定单例模式中的影响

基于双重检验的单例模式(懒汉型)

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

解决办法

用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;
}
}

3 适用场景

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

(2)volatile无法同时保证内存可见性和原子性。加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性

(3)volatile不能修饰写入操作依赖当前值的变量。声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。

(4)当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile;

(5)volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

Java 中 volatile 关键字及其作用的更多相关文章

  1. Java中volatile关键字及其作用是什么?

    在 Java 多线程中如何保证线程的安全性?那我们可以使用 Synchronized 同步锁来给需要多个线程访问的代码块加锁以保证线程安全性.使用 synchronized 虽然可以解决多线程安全问题 ...

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

    转(http://m.jb51.net/article/41185.htm)Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制 在java线 ...

  3. java中volatile关键字的作用

    一.内存模型的相关概念 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存 ...

  4. 【转】java中volatile关键字的含义

    java中volatile关键字的含义   在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  5. 转:java中volatile关键字的含义

    转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  6. 【Java_基础】Java中Native关键字的作用

    本篇博文转载与:Java中Native关键字的作用

  7. Java中Volatile关键字详解 (转自郑州的文武)

    java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 一.基本概念 先补充一下概念:J ...

  8. 就是要你懂Java中volatile关键字实现原理

    原文地址http://www.cnblogs.com/xrq730/p/7048693.html,转载请注明出处,谢谢 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是j ...

  9. 【转】Java学习---Java中volatile关键字实现原理

    [原文]https://www.toutiao.com/i6592879392400081412/ 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.c ...

随机推荐

  1. 201871010110-李华 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告

    项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业要求 我的课程学习目标 (1)理解并掌握代码风格及设计规范:(2)通过任务3进行协作开发,尝试进行代码复审,在进行同伴复审的过程中体会结对 ...

  2. git版本控制之维护旧仓库和往仓库里放货物

    [git bash下 git clone git项目地址:输入这个命令 就会在你运行命令路径下创建一个文件夹,名字就是这个仓库的名字!!这样就完成了把一个别人的旧仓库克隆到自己本地仓库中进行管理维护和 ...

  3. Java(114-132)【Scanner类、Random类、ArrayList类】

    1.API概述和使用步骤 应用程序编程接口.Java的API是一本程序员的字典,学会查询 2.Scanner 概述及其API文档 键盘输入 类都是大写的Scanner,关键字是小写的public 3. ...

  4. WPF-3D圆柱体透视

    3D圆柱体透视效果 总效果 原理: 3D面+面在摄像机方向上的2D投影点的集合 3D面效果: 2D线: 画线时需要注意两个点: 1 在圆柱体上下两个圆之间有两条竖着的棱边代表圆柱体边缘 2 被遮盖的圆 ...

  5. 3. Mybatis Insert

    案例: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC &qu ...

  6. C++ Primer Plus(第6版)中文版电子版awz+epub+mobi三版

    电子书资源:C++ Primer Plus(第6版)中文版 书籍简介:   C++是在C语言基础上开发的一种集面向对象编程.通用编程和传统的过程化编程于一体的编程语言,是C语言的超集.<C++ ...

  7. Appium 简介与自动化测试环境搭建

    1. Appium 简介 2. Appium 自动化测试环境搭建 1. Appium 简介 Appium 是一个开源测试自动化框架,可用于原生,混合和移动 Web 应用程序测试. 它使用 WebDri ...

  8. 1016 Phone Bills

    A long-distance telephone company charges its customers by the following rules: Making a long-distan ...

  9. LINQPad,我的C#/.NET学习诀窍

    LINQPad,我的C#/.NET学习诀窍 在我以往的文章中,尤其涉及代码演示的,都使用了同一个工具--LINQPad.但许多客户面对我分享的.linq源文件都迷茫不知所措,因此有必要来聊聊一下这个强 ...

  10. 关于height:100%

    要想高度百分比起作用,一般来说,要满足两个条件:其一,父标签有高度可寻,就是向上遍历父标签要找到一个定值高度(body,html另外讨论),如果中途有个height为auto或是没有设置height属 ...