怎么理解java关键字volatile(附最新阿里面题)

Java并发编程】10. 一文搞懂volatile 由浅入深,直击痛点 今天闲来无事跟同事小麦大叔闲聊, SoWhat:麦叔听说你偷偷阿里啦,的咋样? 小麦大叔: 一挺简单的,主要问了一些基本的数据结构跟算法,还问了下 HashMap的十大常见基本问。我都答案上来了,还问了我JDK7环,幸亏你那个HashMap环绘制的牛逼,我答的不错就让我准备二了。 SoWhat:二类? 小麦大叔:二问了我一些JVM的问,问我对于JVM内存模型的理解,还有GC的常见... 阅读详情

一、什么是volatile

volatile作为java中的关键词之一,用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并不是是线程A更新后的值)。volatile会禁止指令重排。

二、volatile的原理

volatile具有可见性、有序性,不具备原子性。
注意,volatile不具备原子性,这是volatile与java中的synchronized、java.util.concurrent.locks.Lock最大的功能差异,这一点在面试中也是非常容易问到的点。

  • 原子性:如果你了解事务,那这个概念应该好理解。原子性通常指多个操作不存在只执行一部分的情况,如果全部执行完成那没毛病,如果只执行了一部分,那对不起,你得撤销(即事务中的回滚)已经执行的部分。
  • 可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1、线程2…线程n能够立即读取到线程1修改后的值。
  • 有序性:即程序执行时按照代码书写的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。(本文不对指令重排作介绍,但不代表它不重要,它是理解JAVA并发原理时非常重要的一个概念)。

1.可见性的实现

线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。因此要实现volatile变量的可见性,直接从这方面入手即可。对volatile变量的写操作与普通变量的主要区别有两点:

(1)修改volatile变量时会强制将修改后的值刷新的主内存中。
(2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。

通过这两个操作,就可以解决volatile变量的可见性问题。

2.有序性的实现
在解释这个问题前,我们先来了解一下Java中的happen-before规则,JSR 133中对Happen-before的定义如下:

Two actions can be ordered by a happens-before relationship.If one
action happens before another, then the first is visible to and
ordered before the second.

简单一点说就是如果a happen-before b,则a所做的任何操作对b是可见的。
一起再来看看JSR 133中定义了哪些happen-before规则:

  • 同一个线程中的,前面的操作 happen-before
    后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。

  • 监视器上的解锁操作 happen-before 其后续的加锁操作。(Synchronized 规则)

  • 对volatile变量的写操作 happen-before 后续的读操作。(volatile 规则)

  • 线程的start() 方法 happen-before 该线程所有的后续操作。(线程启动规则)

  • 线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作。

  • 如果 a happen-before b,b happen-before c,则a happen-before c(传递性)。

3、内存屏障

为了实现volatile可见性和happen-befor的语义。JVM底层是通过一个叫做“内存屏障”的东西来完成。内存屏障,也叫做内存栅栏,是一组处理器指令,用于实现对内存操作的顺序限制。下面是完成上述规则所要求的内存屏障:

(1)LoadLoad 屏障
执行顺序:Load1—>Loadload—>Load2

确保Load2及后续Load指令加载数据之前能访问到Load1加载的数据。

(2)StoreStore 屏障
执行顺序:Store1—>StoreStore—>Store2
确保Store2以及后续Store指令执行前,Store1操作的数据对其它处理器可见。

(3)LoadStore 屏障
执行顺序: Load1—>LoadStore—>Store2
确保Store2和后续Store指令执行前,可以访问到Load1加载的数据。

(4)StoreLoad 屏障

执行顺序: Store1—> StoreLoad—>Load2

确保Load2和后续的Load指令读取之前,Store1的数据对其他处理器是可见的。
最后通过一个实例来说明一下JVM中是如何插入内存屏障的:

package com.paddx.test.concurrent;

public class MemoryBarrier {
    int a, b;
    volatile int v, u;

    void f() {
        int i, j;

        i = a;
        j = b;
        i = v;
        //LoadLoad
        j = u;
        //LoadStore
        a = i;
        b = j;
        //StoreStore
        v = i;
        //StoreStore
        u = j;
        //StoreLoad
        i = u;
        //LoadLoad
        //LoadStore
        j = b;
        a = i;
    }
}

三、volatile的使用

volatile适用场景

适用于对变量的写操作不依赖于当前值,对变量的读取操作不依赖于非volatile变量。
适用于读多写少的场景。
可用作状态标志。
JDK中volatie应用:JDK中ConcurrentHashMap的Entry的value和next被声明为volatile,AtomicLong中的value被声明为volatile。AtomicLong通过CAS原理(也可以理解为乐观锁)保证了原子性。

1、防止重排序

package com.paddx.test.concurrent;

public class Singleton {
    public static volatile Singleton singleton;

    /**
     * 构造函数私有,禁止外部实例化
     */
    private Singleton() {};

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

从一个最经典的例子来分析重排序问题。小伙伴应该都比较熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。

现在我们分析一下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:

(1)分配内存空间。
(2)初始化对象。
(3)将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

(1)分配内存空间。
(2)将内存空间的地址赋值给对应的引用。
(3)初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。

2、实现可见性
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。
volatile关键字能有效的解决这个问题,举个列子:

package com.paddx.test.concurrent;

public class VolatileTest {
    int a = 1;
    int b = 2;

    public void change(){
        a = 3;
        b = a;
    }

    public void print(){
        System.out.println("b="+b+";a="+a);
    }

    public static void main(String[] args) {
        while (true){
            final VolatileTest test = new VolatileTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();

        }
    }
}

直观上说,这段代码的结果只可能有两种:b=3;a=3 或 b=2;a=1。不过运行上面的代码(可能时间上要长一点),你会发现除了上两种结果之外,还出现了第三种结果:

...... 
b=2;a=1
b=2;a=1
b=3;a=3
b=3;a=3
b=3;a=1
b=3;a=3
b=2;a=1
b=3;a=3
b=3;a=3
......

为什么会出现b=3;a=1这种结果呢?正常情况下,如果先执行change方法,再执行print方法,输出结果应该为b=3;a=3。相反,如果先执行的print方法,再执行change方法,结果应该是 b=2;a=1。那b=3;a=1的结果是怎么出来的?原因就是第一个线程将值a=3修改后,但是对第二个线程是不可见的,所以才出现这一结果。如果将a和b都改成volatile类型的变量再执行,则再也不会出现b=3;a=1的结果了。

3、保证原子性

关于原子性的问题,上面已经解释过。volatile只能保证对单次读/写的原子性。由于volatile变量对原子性保证,有一个问题容易被误解。现在我们就通过下列程序来演示一下这个问题:

package com.paddx.test.concurrent;

public class VolatileTest01 {
    volatile int i;

    public void addI(){
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        final  VolatileTest01 test01 = new VolatileTest01();
        for (int n = 0; n < 1000; n++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test01.addI();
                }
            }).start();
        }

        Thread.sleep(10000);//等待10秒,保证上面程序执行完成

        System.out.println(test01.i);
    }
}

大家可能会误认为对变量i加上关键字volatile后,这段程序就是线程安全的。大家可以尝试运行上面的程序。下面是我本地运行的结果:

可能每个人运行的结果不相同。不过应该能看出,volatile是无法保证原子性的(否则结果应该是1000)。原因也很简单,i++其实是一个复合操作,包括三步骤:

(1)读取i的值。
(2)对i加1。
(3)将i的值写回内存。

volatile是无法保证这三个操作是具有原子性的,我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。

注:上面几段代码中多处执行了Thread.sleep()方法,目的是为了增加并发问题的产生几率,无其他作用。

四、最新阿里天猫3面(GC回收+Redis Hash算法+架构部署+秒杀等)

一面

自我介绍
jvm GC原理,JVM怎么回收内存
ConcurrentHashMap是怎么解决并发问题的?
HashMap怎么从链表转换为红黑树?如果存入的是null键,放在桶的哪个位置?
谈谈Synchronized与ReentrantLock的区别
再谈谈 threadLocal的实际应用
Java的内存模型,Java8做了什么修改
线程池中的几种重要的参数及流程说明
一次web请求响应中,那个部分最耗时,tcp握手?业务逻辑处理?网络延迟?数据库查询?浏览器解析?
如何进行JVM调优?有哪些方法?

二面
如何理解内存泄漏问题?有哪些情况会导致内存泄露?如何解决?
怎么理解强一致性、单调一致性和最终一致性?
分布式锁有哪些解决方案?
如何解决Redi缓存穿透的问题?
Redis hash算法用的是什么?
Redis集群方案应该怎么做?都有哪些方案?
三面
谈谈你的参与的项目
能否画出整个项目的架构设计图,尽量包含流程、部署等
分布式系统设计你会考虑哪些策略?
有遇见过服务器雪崩的场景?如何避免?
谈谈你对天猫双11秒杀的设计?
为什么想来阿里天猫?
未来3年有什么职业规划?

C#实现图片裁剪 (完整源码) 阅读详情

相关推荐

数据挖掘实战-基于情感分析+网络语义分析+LDA主分析对大唐不夜城用户评论进行文本分析

本研究基于去哪儿网和携程网的1888条大唐不夜城游客评论数据,运用Python进行文本分析。通过情感分析发现游客评价以积极为主(占比约70%),中性占20%,消极占10%。采用ROSTCM6软件进行社会网络语义分析,识别不同情绪下的高频词汇。使用LDA主模型将评论划分为3个主(夜景、表演、美食),并分析主强度随时间变化趋势。实验包含数据采集、预理、情感分析、语义网络分析和LDA建模等完整流程,为景区管理优化提供数据支持。源代码涵盖爬虫、数据理和可视化分析的全套实现。

m0_64336780的博客 1万+

网络

1、

lhx18020483271的博客 506

基于 ConvNeXt 网络对 11种水果、食物图像识别【包含数据集、完整代码等等】

基于 ConvNeXt 网络对11种水果、食物图像识别【包含数据集、完整代码等等】代码全部手写实现,标注完整,方便调试和阅读。训练自定义数据集非常简单,摆放好数据即可,用户也可以根据自身需求添加新的模块【训练脚本】训练的时候,可以选择ConvNeXt对应的tiny、base 五种网络。优化器采用SGD、Adam算法可以自行选择。学习率采用余弦退火算法。损失函数是交叉熵多类别损失。代码自动生成数据集类别的 json 文件,计算网络的输出个数。并且计算训练集的mean和std实现更好的收敛效果。对数据的预理种采用翻转、裁剪等等多种图像增广。训练完成会生成loss、acc在训练、验证集上的曲线,以及分别保存混淆矩阵图,计算相应的召回率、精确度等等,并且还会保存一张预理后的图像【预测predict.py】只需要将待预测的图像放在指定文件夹下,代码会自动将该文件下所有的图像进行预测,并在原图像左上角写入大的前三个类别和概率【代码介绍:https://blog.csdn.net/qq_44886601/article/details/138574202】

2024年试官爱的 volatile 关键字,这些问你都搞懂了没?,字节跳动超高难度三java程序员

我个人认为,如果你想靠着背来获得心仪的offer,用癞蛤蟆想吃天鹅肉形容完全不过分。想必大家能感受到试越来越难,想找到心仪的工作也是越来越难,高薪工作羡慕不来,却又对自己目前的薪资不太满意,工作几年甚至连一个应届生的薪资都比不上,终究是错付了,错付了自己没有去提升技术。这些分享给大家的目的,其实是希望大家通过大厂分析自己的技术栈,给自己梳理一个更加明确的学习方向,当你准备好去试大厂,你心里有底,大概知道试官会问多广,多深,避免试的时候一问三不知。

2401_84688180的博客 1179

Java并发编程volatile关键字解析

Java并发编程volatile关键字解析 volatile发音:美[ˈvɑ:lətl] **不稳定的、易变的。 并发编程的三个概念:原子性、可见性、有序性 缓存变量是否有效的标志:缓存行 volatile修饰的变量,值的改变会立即刷新到主存中。线程1和线程2都对一个变量作读取操作之后,若线程1改变了值,则线程2中该变量的缓存行会变为无效。所以,要重新从主存中读取该变量的值。 注:vo...

【小喵】的博客 270

Java--Java并发(volatile

线程 A 首先得到了 i 的初始值100,但是还没来得及修改,就阻塞了,这时线程 B 开始了,它也得到了 i 的值,由于 i 的值未被修改,即使是被 volatile 修饰,主存的变量还没变化,那么线程 B 得到的值也是100,之后对其进行加 1 操作,得到101后,将新值写入到缓存中,再刷入主存中。一个变量 i 被 volatile 修饰,两个线程想对这个变量修改,都对其进行自增操作也就是 i++,i++ 的过程可以分为三步,首先获取 i 的值,其对 i 的值进行加1,后将得到的新值写会到缓存中。

SuZhan0711 1466

2019 阿里天猫Java 3,含答案!

1 阿里天猫Java 自我介绍 jvm GC原理,JVM怎么回收内存 ConcurrentHashMap是怎么解决并发问的? HashMap怎么从链表转换为红黑树?如果存入的是null键,放在桶的哪个位置? 谈谈Synchronized与ReentrantLock的区别 再谈谈 threadLocal的实际应用 Java的内存模型,Java8做了什么修改 线程池中的几种重要的参数及流程说明 一web请求响...

一个有情怀的程序猿博客 592

【JUC并发编程 】 详解Java关键字volatile

Java关键字 volatile 你了解多少呢,详解 volatile

wanghuichen的博客 1517

如何彻底理解volatile关键字

导读试,你又被volatile关键字虐了吗?这个问,是不是问得有点扎心了!的确,有很多朋友反馈试中在涉及考察Java并发编程知识的时候,经常会被问到volati...

JAVA葵花宝典 581

volatile关键字的作用_java编程,如何彻底理解volatile关键字

导读试,你又被volatile关键字虐了吗?这个问,是不是问得有点扎心了!的确,有很多朋友反馈试中在涉及考察Java并发编程知识的时候,经常会被问到volatile关键字。对于有些公司如果你能回答出volatile关键字的基本作用及原理,如:"volatile关键字可以实现线程间的可见性,之所以可以实现这一点,原因在于JVM会保证被volatile修饰的变量,在线程栈中被线程使用时都会主...

weixin_39613712的博客 88

一篇文章彻底理解volatile关键字

试,你又被volatile关键字虐了吗?这个问,是不是问得有点扎心了!的确,有很多朋友反馈试中在涉及考察Java并发编程知识的时候,经常会被问到volatile关键字。对于有些公司如果你能回答出volatile关键字的基本作用及原理,如:"volatile关键字可以实现线程间的可见性,之所以可以实现这一点,原因在于JVM会保证被volatile修饰的变量,在线程栈中被线程使用时都会主动从共享内存(堆内存/主内存)中以实时的方式同步一;另一方,如果线程在工作内存中修改了volatile修饰的变.

wzwsq的博客 256

Java进阶DAY32-03-JUC扩展 3 volatile关键字

分析如下程序,说出在控制台的输出结果。Thread的子类测试类控制台输出结果前是false,过了一段时间之后就变成了true按照我们的分析,当我们把volatileThread线程启动起来以后,那么volatileThread线程开始执行。在volatileThread线程的run方法中,线程休眠1s,休眠一秒以后那么flag的值应该为true,此时我们在主线程中不停的获取flag的值。发现前释放false,后是true信息,那么这是为什么呢?要想知道原因,那么我们就需要学习一下JMM。

tang7mj的博客 787

Java并发系列(5)java关键字-volatile

本站小福利 点我获取阿里云优惠券 原文作者:github:CL0610/Java-concurrency 免责声明: 1.本文所转载文章均来自公开网络。 2.如果出标注有误或侵犯到原著作者权益,请联系删除。 3.转载文章请注明原文链接和作者,否则产生的任何版权纠纷均与本站无关。 1. volatile简介 在上一篇文章中我们深入理解java关键字synchronized,我们知道在java中还...

不想当码农的程序员 177

反制试官 | 14张原理图 | 再也不怕被问 volatile!

反制试官 | 14张原理图 | 再也不怕被问 volatile! 悟空 爱学习的程序猿,自主开发了Java学习平台、PMP刷小程序。目前主修Java、多线程、SpringBoot、SpringCloud、k8s。本公众号不限于分享技术,也会分享工具的使用、人生感悟、读书总结。 絮叨 这一篇也算是Java并发编程的开篇,看了很多资料,但是轮到自己去整理去总结的时候,发现还是要多看几遍资料才能完全理解。还有一个很重要的点就是,画图是加深印象和检验自己是否理解的一个非常好的方法。 一、Volati

悟空聊架构的专栏 1103

天猫技术部Java开发三

  天猫双11成交再刷新了新的世界记录2135亿,我们一起来看看天猫Java开发技术部的三目。   01 — 天猫一 介绍自己,项目 线程创建方式、线程池、哪些参数?饱和策略? 哪些设计模式?Java用到了哪些设计模式?了解动态代理模式么? spring用到的设计模式? 一web请求响应中,那个部分耗时tcp握手?业务逻...

LMAKE_nbsp的博客 1112

Java1.8中HashMap原理(桶和桶内链表红黑树)

目录 一、HashMap介绍 二、HashMap原理图 三、桶的个数必定为2的n幂 四、HashMap中的put方法 五、链表转红黑树 一、HashMap介绍 HashMap采用<Key,Value>键值对的形式存储数据,在提取数据时可以通过对key的比较得到对应的Value的值. 二、HashMap原理图 HashMap中存储数据是通过存入key的hash值进...

shenshaoming的博客 8850

HashMap里key为null存放到哪?

我们知道HashMap集合是允许存放null值的hashMap是根据key的hashCode来寻找存放位置的,那当key为null时, 怎么存储呢?在put方法里头,其实第一行就理了key=null的情况。 // HashMap的put方法 public V put(K key, V value) { if (table == EMPTY_TABLE) { ...

冯三思 2万+
上一篇: redis持久化底层原理-RDB与AOF
下一篇: 高级java人员都用过的17个jvm参数
Formor_
博客等级 码龄7年 7粉丝 31原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值