volatile关键字及其作用

volatile关键字及其作用概述:本文主要介绍Java语言中的volatile关键字,内容涵盖volatile的保证内存可见性、禁止指令重排等。

大家好,又见面了,我是你们的朋友全栈君。

概述:本文主要介绍Java语言中的volatile关键字,内容涵盖volatile的保证内存可见性、禁止指令重排等。

1 保证内存可见性

1.1 基本概念

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

1.2 实现原理

  当对非volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。
  volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量都从主内存中读,跳过CPU cache这一步。当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。


这里写图片描述

2 禁止指令重排

2.1 基本概念

  指令重排序是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。指令重排序包括编译器重排序和运行时重排序。
  在JDK1.5之后,可以使用volatile变量禁止指令重排序。针对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内存屏障插入策略:

  1. 每个volatile写操作的前面插入一个StoreStore屏障;
  2. 在每个volatile写操作的后面插入一个StoreLoad屏障;
  3. 在每个volatile读操作的后面插入一个LoadLoad屏障;
  4. 在每个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中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/138107.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • quartz调度问题 没报错也没停止运行 Scheduler class: ‘org.quartz.core.QuartzScheduler‘ – running locally

    quartz调度问题 没报错也没停止运行 Scheduler class: ‘org.quartz.core.QuartzScheduler‘ – running locallySchedulerclass:‘org.quartz.core.QuartzScheduler’-runninglocally.NOTSTARTED.Currentlyinstandbymode.Numberofjobsexecuted:0Usingthreadpool‘org.quartz.simpl.SimpleThreadPool’-with20…

    2022年7月27日
    5
  • TCPDF_程序说明文档

    TCPDF_程序说明文档TCPDF说明文档一、首先调用TCPDF文件require_once(‘tcpdf.php’);二、实例化TCPDF类页面方向(P=肖像,L=景观)、测量(mm)、页面格式$pdf=newTCPDF(‘P’,’mm’,’A4′,true,’UTF-8′,false);三、设置文档信息设置文档信息—-文件创作者…

    2025年10月2日
    5
  • php递归函数详解_用php递归函数实现阶乘计算

    php递归函数详解_用php递归函数实现阶乘计算本节内容:PHP递归算法。PHP递归算法代码:复制代码代码示例://定义PI一分的角度的值define(“PII”,M_PI/180);//新建图像资源,并定义其背景为白色,前景色为黑色$im=imagecreate(670,500);$white=imagecolorallocate($im,0xFF,0xFF,0xFF);$g=imagecolorallocate($im,0x00,0x0…

    2022年8月11日
    5
  • 异步fifo设计注意事项有哪些(陈设设计)

    近日学习用到异步FIFO,故写下这篇博客记录学习心得。在我看来,异步FIFO的设计注意事项大体分为两点:亚稳态的处理空满状态的判断第一,来说一下亚稳态。亚稳态是指触发器无法在某个规定时间内达到一个可确认的状态。在FIFO的设计中,产生的原因主要由两点:一是逻辑电平的误判,也就是如果通过二进制作为指针来判断空满状态,因二进制数值变化引起的位数变化大,对电路的危害也随之增加,故在本设计中使…

    2022年4月15日
    59
  • 【学习强化学习】十三、模仿学习介绍[通俗易懂]

    【学习强化学习】十三、模仿学习介绍[通俗易懂]文章目录参考资料1.模仿学习概述2.行为克隆2.1行为克隆缺点缺点1:观测非常有限缺点2:机器会完全模仿专家的行为缺点3:训练数据跟测试数据不匹配2.逆强化学习2.1概述2.2奖励函数2.2IRLvsGAN3.第三人称视角模仿学习4.练习4.1keywords参考资料https://datawhalechina.github.io/easy-rl/#/chapter11/chapter111.模仿学习概述模仿学习(imitationlearning,IL)又叫做示范学习(

    2022年9月19日
    3
  • HorizontalScrollView 详解[通俗易懂]

    HorizontalScrollView 详解[通俗易懂]2019独角兽企业重金招聘Python工程师标准>>>…

    2022年7月14日
    20

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号