【Java基础】volatile关键字

这篇具有很好参考价值的文章主要介绍了【Java基础】volatile关键字。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

关于作者:CSDN内容合伙人、技术专家, 从零开始做过日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

一、导读

我们继续总结学习Java基础知识,温故知新。

二、概览

volatile 是一个Java关键字,可以用来修饰变量,volatile也被称为轻量级的synchronized,运行时开销比 synchronized更小。

2.1 作用

1、确保共享变量在线程之间的同步,实现可见性。
2、禁止处理器重排序。

2.2 多线程共享变量的访问流程

线程执行时,先拷贝主存数据到本线程本地,操作完成后再把结果从线程本地刷到主存。
我们看下面的图,多线程时,共享变量操作完的值是在红色的区域。
【Java基础】volatile关键字

2.3 多线程为什么会出现可见性问题

可见性是由于CPU缓存引起,CPU 增加了缓存,以均衡与内存的速度差异,导致 可见性问题。
在多线程环境下,多个线程同时访问共享变量,由于不同线程的执行顺序和时间不确定,可能会导致一个线程对共享变量的修改在其他线程中不可见。可参考上面共享变量的访问流程。

2.4 volatile如何实现可见性

volatile不允许线程内进行缓存和重排序,直接修改内存,所以对其他线程是可见的。

被volatile修饰的变量读写时,都会直接刷到主存,从而使得变量可见。

2.5 如何实现禁止指令重排序

volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。

当一个变量被修饰时,表示变量是“易变的”(volatile)或者“不稳定的”(unstable),它意味着该变量的值可能会被其他线程(或进程)修改。

注意: volatile 关键字只能保证线程之间的同步,不能保证线程安全
要保证线程安全,需要使用其他同步机制,比如 synchronized 关键字或者 Lock 接口。

volatile具有可见性、有序性,但不具有原子性,所以是线程不安全的。

2.6 举例

  • 不使用volatile关键字
禁止线程缓存变量结果。
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。
引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。
举例:
// Thread-A
new Thread("Thread A") {
    @Override
    public void run() {
        while (!stop) {
        }
        System.out.println(Thread.currentThread() + " stopped");
    }
}.start();

一个线程内使用了停止的开关,假如这个stop没有被volatile修饰,我们在线程b中修改,
线程a并不知道开关的值被修改了。
  • 使用volatile 防重排序
从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现
public class Singleton {
public static volatile Singleton singleton;
    /**
    * 构造函数私有,禁止外部实例化
    */
    private Singleton() {};
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

实例化一个对象其实可以分为三个步骤:
* 分配内存空间。
* 初始化对象。
* 将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
* 分配内存空间。
* 将内存空间的地址赋值给对应的引用。
* 初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。
因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
  • 使用volatile 保证原子性:单次读/写
volatile变量的单次读/写操作可以保证原子性的,如longdouble类型变量,
但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作,要保证多步的原子性,
可以通过AtomicInteger或者Synchronized来实现,本质上就是cas操作。

2.7 来答题

  • 问题1: i++为什么不能保证原子性?
i++其实是一个复合操作,包括三步骤:
* 读取i的值。
* 对i加1* 将i的值写回内存。 
volatile是无法保证这三个操作是具有原子性的,
我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。 
  • 问题2: 共享的long和double变量的为什么要用volatile?
因为longdouble两种数据类型的操作可分为高32位和低32位两部分,
因此普通的longdouble类型读/写可能不是原子的。
因此,鼓励大家将共享的longdouble变量设置为volatile类型,
这样能保证任何情况下对longdouble的单次读/写操作都具有原子性

三、原理

在JVM底层volatile是采用“内存屏障”来实现的,加入volatile关键字时,会多出一个lock前缀指令
内存屏障,又称内存栅栏,是一个 CPU 指令。

1、用javac命令进行编译生成.class文件,
2、再用javap命令反编译查看.class文件的信息,就可以看到字节码信息中多了一些指令。

为了保证各个处理器的缓存是一致的,实现了缓存一致性协议(MESI),每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

volatile 变量通过这样的机制就使得每个线程都能获得该变量的最新值。

3.2 使用场景:

解决对变量可见性有要求,但是对读取顺序没要求的需求。

  • volatile特性:
    (1)volatile仅能使用在变量级别
    (2)volatile仅能实现变量的修改可见性,不能保证原子性,volatile + cas 就实现了原子性,如atomic包下面的类。
    (3)volatile不会造成线程的阻塞
    (4)volatile标记的变量不会被编译器优化

四、 推荐阅读

【Java基础】原子性、可见性、有序性

【Java基础】java可见性之 Happens-before

【Java基础】java-android面试Synchronized

【Java基础】java-android面试-线程状态

【Java基础】线程相关

【Java基础】java 异常

【Java基础】java 反射

【Java基础】java 泛型

【Java基础】java注解

【Java基础】java动态代理

【Java基础】Java SPI

【Java基础】Java SPI 二 之 Java APT

【Java基础】 jvm 堆、栈、方法区 & java 内存模型文章来源地址https://www.toymoban.com/news/detail-513658.html

到了这里,关于【Java基础】volatile关键字的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 关于c++中mutable、const、volatile这三个关键字及对应c++与汇编示例源码

    这哥三之间的关系是有趣的,不妨看看这个: cv (const and volatile) type qualifiers - cppreference.com permits modification of the class member declared mutable even if the containing object is declared const. 即便一个对象是const的,它内部的成员变量如果被mutable修饰,则此成员变量依旧可以被修改。 很常见,

    2024年02月13日
    浏览(26)
  • volatile关键字作用

    volatile是一个和多线程相关的,主要有一下2点作用(只保证可见性,不保证原子性) 防止指令重排(有序性) JVM在不改变程序执行结果的前提下,在编译时会对指令的顺序进行重新排序,而volatile则能够禁止指令的重新排序 能够确保线程内存中的对象对其他内存可

    2024年02月15日
    浏览(34)
  • volatile 关键字详解

    目录 volatile volatile 关键用在什么场景下: volatile 防止编译器优化: volatile   是一个在许多编程语言中(包括C和C++)用作的标识符。它用于告诉编译器不要对带有该修饰的变量进行优化,以确保变量在特定情况下的可见性和预测性。 在C和C++中, volatile

    2024年02月11日
    浏览(33)
  • 【多线程】volatile关键字

    一、volatile 1.volatile的底层原理是内存屏障,Memory Barrier, Memory Fence 2.对volatile变量的写指令(赋值操作)后会加入写屏障 3.对volatile变量的读指令(取变量值)前会加入读屏障 4.写屏障的作用会将写屏障之前的赋值改动操作,对共享变量的改动都同步到主内存中 5.读屏障的作

    2024年02月06日
    浏览(32)
  • 【C】volatile 关键字

    1)基本概念 const 是C语言的一个。 const 用于告诉编译器相应的变量可能会在程序的控制之外被修改,因此编译器不应该对其进行优化。 声明语法: 作用: 防止编译器优化,确保对变量的每次访问都是实际的读写操作,而不是使用缓存中的值。 用于表示可能会 被异步

    2024年01月22日
    浏览(36)
  • [JAVAee]volatile关键字

    目录 1.volatile的特性 ①保持线程可见性 2.volatile注意事项及适用场景 ①注意事项 ②适用场景 volatile,译为\\\"易变的\\\". 对此我们就可以这样理解,对于被volatile修饰的变量的数值,是容易变化的. 在之前的线程安全文章中,我们有讲解过\\\"可见性\\\",对于线程间的这个特性可能会导致:线程

    2024年02月16日
    浏览(28)
  • C语言volatile关键字

    在C语言中, volatile 是一个类型修饰符,用于告诉编译器对象的值可能会在编译器无法检测到的情况下被改变。这通常发生在以下两种情况: 硬件的输入/输出操作,例如一个设备寄存器的读取或写入。 共享内存的并行程序,其中一个线程修改了一个内存位置,而另一个线程

    2024年02月07日
    浏览(38)
  • volatile关键字(轻量级锁)

    目录 一、volatile出现背景 二、JMM概述 2.1、JMM的规定  三、volatile的特性 3.1、可见性  3.1.1、举例说明  3.1.2、总结 3.2、无法保证原子性 3.2.1、举例说明 3.2.2、分析 3.2.3、使用volatile对原子性测试  3.2.4、使用锁机制  3.2.5、总结 3.3、禁止指令重排序  四、volatile的内存语义 4

    2024年02月15日
    浏览(31)
  • 关于哪些java关键字

    放有道笔记里面东西太多,整理整理放出来 1: 关于static: 》在不实例化对象的情况下访问变量或者调用方法,常用的如各种工具类,无状态且无需实例化对象,直接调用。 》static代码块,常用来在加载class的时候就初始化且只有一次:配置文件加载,keystore/truststore初始化。

    2024年02月03日
    浏览(24)
  • 多线程系列(四) -volatile关键字使用详解

    在上篇文章中,我们介绍到在多线程环境下,如果编程不当,可能会出现程序运行结果混乱的问题。 出现这个原因主要是,JMM 中主内存和线程工作内存的数据不一致,以及多个线程执行时无序,共同导致的结果。 同时也提到引入 synchronized 同步锁,可以保证线程同步,让多

    2024年02月21日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包