Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Volatile关键字能保证原子性么?

Volatile关键字能保证原子性么?

作者头像
Java极客技术
发布于 2022-12-04 05:30:12
发布于 2022-12-04 05:30:12
41200
代码可运行
举报
文章被收录于专栏:Java极客技术Java极客技术
运行总次数:0
代码可运行

说到这个 volatile 这个关键字,阿粉觉得看过阿粉文章的,肯定都对这个关键字那是非常的熟悉的,因为做Java开发的,在面试的时候,如果涉及到多线程,那么面试官有不少人会询问关于 volatile 这个关键字的使用,以及他的作用,今天阿粉就来说说这个 volatile 关键的的作用,以及他的一些特性。

volatile

volatile 是 Java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量。

而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可见性。

那么什么是有序性,什么又是可见性呢?

有序性

那么什么是有序性呢?

其实程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序。

看似理所当然,其实并不是这样,指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

但是在多线程环境下,有些代码的顺序改变,有可能引发逻辑上的不正确。

而 volatile 就是因为有这个特性,所以才被大家熟知的。

volatile 又是如何保证有序性的呢?

有很多小伙伴就说,网上说的是 volatile 可以禁止指令指令重排序,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被 volatile 修饰的变量的操作,会严格按照代码顺序执行,就是说当代码执行到 volatile 修饰的变量时,其前面的代码一定执行完毕,后面的代码一定没有执行。

如果这时候,面试官不再继续深挖下去的话,那么恭喜你,可能这个问题已经回答完了,但是如果面试官继续往下深挖,为什么会禁止指令重排,什么又是指令重排呢?

在从源码到指令的执行,一般是分成了三种重排,如图所示

我们接下来就得看看 volatile 是如何禁止指令重排的。

我们直接用代码来进行验证。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ReSortDemo {

    int a = 0;
    boolean flag = false;

    public void mehtod1(){
        a = 1;
        flag = true;
    }

    public void method2(){
        if(flag){
            a = a +1;
            System.out.println("最后的值: "+a);
        }
    }
}

如果有人看到这段代码,肯定会说,那这段代码出来的结果会是什么呢?

有些人说是 2,是的, 如果你只是单线程调用,那结果就是 2,但是如果是多线程调用的时候,最后的输出结果不一定是我们想象到的 2,这时就要把两个变量都设置为 volatile。

如果大家对单例模式了解比较多的话,肯定也是关注过这个 volatile,为什么呢?

大家看看如下代码,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Singleton {
 // 不是一个原子性操作
 //private static Singleton instance;
 //改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
 private static volatile Singleton instance;

 // 构造器私有化
 private Singleton() {
 }

 // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用
 public static Singleton getInstance() {
  if (instance == null) {
   synchronized (Singleton.class) {
    if (instance == null) {
     instance = new Singleton();
    }
   }
  }
  return instance;
 }
}

上面的单例模式大家熟悉么?

是的,这就是 **双重检查(DCL 懒汉式) **

有人会说,因为有指令重排序的存在,双端检索机制也也不一定是线程安全的呀,对呀,所以阿粉用到了 synchronized 关键字,让他变成了线程安全的了。

可见性

其实可见性就是,在多线程环境中,对共享变量的修改对于其他线程是否立即可见的问题。

那么他的可见性一般都会表现在什么地方呢?用在什么地方呢?

其实在阿粉的感知中,一般用这个变量,很多都是为了保证他的可见性,就比如定义的一个全局变量,在其中有个循环来判断这个变量的值,有一个线程修改了这个参数的时候,这个循环会停止,跳转到之后去执行。

我们来看看没有使用volatile修饰代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Test {

    private static boolean flag = false;

    public static void main(String[] args) throws Exception{
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程A开始执行:");
                for (;;){
                    if (flag){
                        System.out.println("跳出循环");
                        break;
                    }
                }
            }
        }).start();
        Thread.sleep(100);

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程B开始执行");
                flag = true;
                System.out.println("标识已经变更");
            }
        }).start();
    }

}

结果大家肯定是可想而知,

运行结果肯定是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
线程A开始执行:
线程B开始执行
标识已经变更

确实,就是这样的。

如果我们用 volatile 呢,那么这个代码的执行结果就会不一样呢?

我们来试一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Test {

    private static volatile boolean flag = false;

    public static void main(String[] args) throws Exception{
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程A开始执行:");
                for (;;){
                    if (flag){
                        System.out.println("跳出循环");
                        break;
                    }
                }
            }
        }).start();
        Thread.sleep(100);

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程B开始执行");
                flag = true;
                System.out.println("标识已经变更");
            }
        }).start();
    }

这样我们就能看到另外一个执行结果,在循环当中的输出语句是可以被执行的。

也就是说,在线程B 中,我们去修改这个被修饰的变量,那么最终,在线程A中,就能顺利读取到我们的数据信息了。

是否能够保证原子性

不能,我们来看一点代码,被volatile修饰的变量,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Test {

    // volatile不保证原子性
    // 原子性:保证数据一致性、完整性
    volatile int number = 0;

    public void addPlusPlus() {
        number++;
    }

    public static void main(String[] args) {
        Test volatileAtomDemo = new Test();
        for (int j = 0; j < 20; j++) {
            new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    volatileAtomDemo.addPlusPlus();
                }
            }, String.valueOf(j)).start();
        }// 后台默认两个线程:一个是main线程,一个是gc线程
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        // 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000
        System.out.println(Thread.currentThread().getName() +
                " final number result = " + volatileAtomDemo.number);
    }

}

如果能够保原子性,那么最终的结果应该是20000,但是每次的最终结果并不能保证就是20000,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
main final number result = 17114
main final number result = 20000
main final number result = 19317

三次执行,都是不同的结果,

为什么会出现这种呢?这就和number++有关系了

number++被拆分成3个指令

  • 执行GETFIELD拿到主内存中的原始值number
  • 执行IADD进行加1操作
  • 执行PUTFIELD把工作内存中的值写回主内存中

当多个线程并发执行PUTFIELD指令的时候,会出现写回主内存覆盖问题,所以才会导致最终结果不为 20000,所以 volatile 不能保证原子性。

所以,你知道怎么回答了么?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java极客技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解Java内存模型(JMM)与volatile关键字,彻底搞懂并发编程的基石,面试官都点赞!
你有没有遇到过这样的情况:明明写了一段看起来没问题的多线程代码,可运行结果却出人意料?要是你已经被这些并发问题折磨过,那就得好好了解Java内存模型和volatile关键字了。跟我一起来看看,怎样彻底搞懂这个面试常考点!
格姗知识圈
2025/06/16
1120
深入理解Java内存模型(JMM)与volatile关键字,彻底搞懂并发编程的基石,面试官都点赞!
volatile 详解
JMM本身是一种抽象的概念模型并不真实存在,塔描述的是一组规则或规范,通过这组规范定义了程序中各个变量(实例字段、静态字段、构成数组对象的元素)的访问方式
ruochen
2021/12/16
1.6K0
面试官最爱的volatile关键字
在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性都牵扯出来,深入地话还可以考察JVM底层实现以及操作系统的相关知识。 下面我们以一次假想的面试过程,来深入了解下volitile关键字吧! 面试官: Java并发这块了解的怎么样?说说你对volatile关键字的理解 就我理解的而言,被volatile修饰的共享变量,就具有了以下两点特性: 1.保证了不同线程对
程序员鹏磊
2018/02/09
6350
面试官最爱的volatile关键字
[Java] volatile 详详解![通俗易懂]
JMM:JAVA内存模型(java memory model) 是一种抽象概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式。
全栈程序员站长
2022/09/08
2070
[Java] volatile 详详解![通俗易懂]
Java面试官最爱问的volatile关键字
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
程序新视界
2019/11/12
7330
Java volatile 关键字解释 用法原理 并发编程特性
有时仅仅为了读写一个或者两个实例域就使用同步的话,显得开销过大,volatile关键字为实例域的同步访问提供了免锁的机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。再讲到volatile关键字之前我们需要了解一下内存模型的相关概念以及并发编程中的三个特性:原子性,可见性和有序性。
大鹅
2021/06/16
4630
Java并发编程:volatile关键字解析
volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在Java 5之后,volatile关键字才得以重获生机。
用户6182664
2019/09/05
5890
Java并发-2.volatile关键字
用 volatile 关键字修饰一个共享变量(类的成员变量,类的静态成员变量)后,有两个作用:
悠扬前奏
2019/05/28
3100
反制面试官 | 14张原理图 | 再也不怕被问 volatile!
这一篇也算是Java并发编程的开篇,看了很多资料,但是轮到自己去整理去总结的时候,发现还是要多看几遍资料才能完全理解。还有一个很重要的点就是,画图是加深印象和检验自己是否理解的一个非常好的方法。
悟空聊架构
2020/08/19
3640
6.volatile与JMM
cheese
2024/03/25
900
6.volatile与JMM
Java volatile关键字使用与原理分析
正确的使用场景,基本符合一个原则: 一写多读:有一个数据,只由一个线程更新,其他线程都来读取。
大发明家
2021/12/15
2860
从计算机的角度理解volatile关键字
我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区。而刚开始用户磁盘和CPU进行交互,CPU运转速度越来越快,磁盘远远跟不上CPU的读写速度,才设计了内存,但是随着CPU的发展,内存的读写速度也远远跟不上CPU的读写速度,因此,为了解决这一矛盾,CPU厂商在每颗CPU上加入了高速缓存,用来缓解这种症状,因此,现在CPU同内存交互就变成了下面的样子。
Java识堂
2019/08/13
4930
你真的了解 volatile 关键字吗?
The Java programming language provides a secondmechanism, volatile fields, that is more convenient than locking for somepurposes. A field may be declared volatile, in which case the Java Memory Modelensures that all threads see a consistent value for the variable.
武培轩
2019/10/31
8720
Java并发篇:volatile关键字吐血整理「建议收藏」
在上一篇文章中我们已经知道线程是 通过主内存 去进行线程间的 隐式通信 的,而线程对共享变量的写操作在 工作内存 中完成,由JMM控制 共享变量由工作内存写回到主内存的时机 。
全栈程序员站长
2022/09/08
4700
聊聊并发编程:volatile关键字
上一篇学习了synchronized的关键字,synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁,而volatile是一个轻量级的同步机制。
烂猪皮
2023/09/04
2290
聊聊并发编程:volatile关键字
面试官提问:说说你对volatile关键字的理解?
在上篇文章中,我们介绍到在多线程环境下,如果编程不当,可能会出现程序运行结果混乱的问题。
Java极客技术
2023/09/21
2620
面试官提问:说说你对volatile关键字的理解?
volatile详解、原理
Java允许线程访问共享变量。为了确保共享变量能被一致、可靠地更新,线程必须确保 它是排他性地使用此共享变量,通常都是获得对这些共享变量强制排他性的同步锁。Java编程语言提供了另一种机制,volatile域变量,对于某些场景的使用 这会更加的方便。可以把变量声明为volatile,以让Java内存模型来保证所有线程都能看到这个变量的同一个值。
寻求出路的程序媛
2024/05/13
1860
volatile详解、原理
JAVA 并发编程(一)volatile关键字
volatile是Java中的关键字,用来修饰会被不同线程访问和修改的变量。JMM(Java内存模型)是围绕并发过程中如何处理可见性、原子性和有序性这3个特征建立起来的,而volatile可以保证其中的两个特性。
小石头
2022/11/10
2500
Java volatile关键字
Java 内存模型(JMM)是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
IT小马哥
2021/09/03
3110
Java volatile关键字
java面试题:谈谈你对volatile的理解
  最近打算整理下Java面试中频率比较高,相对比较难的一些面试题,感兴趣的小伙伴可以关注下。
用户4919348
2020/04/16
1.2K0
相关推荐
深入理解Java内存模型(JMM)与volatile关键字,彻底搞懂并发编程的基石,面试官都点赞!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验