5.互联网大厂高频面试题-volatile

请谈谈你对volatiel的理解?

volatile是什么

首先JUC指的是java的三个包:
在这里插入图片描述
首先这个关键字在你日常的单线程工作环境下你是用不到的。
在这里插入图片描述
它的3大特性:
在这里插入图片描述

JMM内存模型之可见性

在这里插入图片描述

JMM是什么

在这里插入图片描述
在这里插入图片描述
读取速度而言,硬盘<内存,磁盘的io达到了一定的阈值之后,很难突破,除非硬件有突破。所以,部分数据,别存在mysql了,存在内存上,比内存再快的就是cpu(这里的快不是读取的概念了)。如果cpu计算的快,内存来不及喂,就不能完全发挥性能,这时候就需要在cpu和内存之间有个cache(缓存)。这里可以参考一下cpu-z软件里面“缓存”的概念。

在这里插入图片描述
假设多个线程,读取主内存(理解为你的8g内存条)上的变量。每个线程都有自己的工作内存,会把变量拷贝回自己的工作内存中,主物理内存中的不动,然后各个线程修改自己的变量。修改完了之后,再写回主物理内存,但是写回去之后,其他线程并不知道你改完之后变量的值,还是拿的旧变量的拷贝。这个数据同步问题就需要解决了。这个及时通知的需要,就称为JMM的内存“可见性”。
在这里插入图片描述
因为通信传值必须通过主内存完成,所以无法线程间进行访问拷贝变量的访问和修改。所以工作内存中改完之后要及时写回主内存空间,然后通知其他线程,这种及时通知的特性就是jmm的可见性。
在这里插入图片描述

JMM三大特性-可见性的代码验证说明

谈谈JMM,如下图:
在这里插入图片描述
对比volatile的三大特性,JMM的特性他遵守了两个,不保证原子性,原子性可以通过sync关键字保证。

代码验证

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果可见性被触发,那么main线程会结束。不加volatile,main会一直等待。也就是说main没有看到其他线程的修改。
在这里插入图片描述
只要加入volatile关键字,就不一样了。增加了JMM的可见性。
在这里插入图片描述

volatile不保证原子性

可见性描述:核心是及时通知
在这里插入图片描述
原子性是什么意思?
在这里插入图片描述
案例演示:
在这里插入图片描述
在这里插入图片描述
原子性就是保证最终的一致性。操作不要被砍成多段。
在这里插入图片描述
执行结果:
在这里插入图片描述
很明显,没有满足原子性,最终的计算结果根本不对!

不保证原子性的理论解释

因为不保证原子性,就会出现丢数据的情况。
在这里插入图片描述
最简单的解决办法,是把并发变成单向车道,也就是加上同步锁,如下:
在这里插入图片描述
但是sync,是大材小用了,太重量级了,性能低。
为什么会出现原子性问题?看下图
在这里插入图片描述

多线程场景,3个线程都把变量的快照读到了自己的工作空间,当1号线程,改掉了变量值,要写回主内存的时候,被挂起,此时2线程执行修改的操作,并写回了主内存,此时1线程恢复,并继续执行,把修改好的数据写回到主内存,它并不知道主内存中的变量值被修改过了,就出现了对2线程修改结果的覆盖,也就造成了丢值的现象,最终的结果就不准了。
虽然是i++,一句java代码,但是class字节码却不只1行,执行过程中,在多线程环境下,是会被挂起,中途执行中断。
看一下i++ 的字节码:
在这里插入图片描述
其中的字节码解释:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个发生过程是这样的:线程123都拿到了主内存的n值,都执行iadd加1操作,这个过程发生在工作内存,而不是主内存,所以操作完了之后,要写回去,执行putfield方法,正常应该是,顺序读写,这时候最终结果就是对的,但是如果有一个线程被挂起,另外两给被唤醒继续执行,这个过程十分快,就容易拿不到最新的值,值写丢了,产生了覆盖。

不保证原子性的解决方案

volatile是轻量级的同步机制,乞丐版的sync,不能保证原子性,想要解决这个问题,方案为:

  • sync同步锁
  • JUC包提供的原子类atomic。
    代码案例:
    mydate类中的变量:原子类的操作,先获得再++,就不会出现上述的情况了,整个过程是不会被中断的。
    在这里插入图片描述

测试代码:
在这里插入图片描述

执行结果:
在这里插入图片描述
为什么原子类可以解决?因为底层是cas算法实现的。说一下cas,围绕自旋锁和unsafe类讲一下。

指令重排案例1

有序性(指令重排):
在这里插入图片描述
数据依赖性:举个例子,要先定义,再赋值,这叫数据依赖性。定义一定在赋值执行的前面。
这个其实很好理解,考试的时候,你做题的顺序,不一定是按照题号来,肯定是先做简单的,再做难得,一切为了得高分(性能好),这就是指令重排。
案例一:
在这里插入图片描述
正常顺序为1234.因为指令重排会变成:
在这里插入图片描述
需要注意的是,因为数据依赖性,不可能出现4123。
案例2:
在这里插入图片描述
这种重排,对业务产生了影响了,就必须得禁止。

指令重排2

指令重排,编译器认为这是种优化,但是多线程环境下,可能会对业务产生产生影响。
这时候就需要用volatile禁止重排!
案例3:
在这里插入图片描述
如果是单线程,执行了method1,再执行method,a是6.
但是如果是多线程发生了指令重排,因为线程交替执行的存在,可能先执行flag=true,之后被挂起,然后其他线程切入执行method2,此时打印的a是5,结果就错了!
在这里插入图片描述
在这里插入图片描述
总结:满足JMM三大特性之后,多线程场景就可以理解为线程安全的。
在这里插入图片描述

多线程下单例模式存在安全问题

在这里插入图片描述

懒汉式

在这里插入图片描述
简单单线程环境测试:
在这里插入图片描述
证明是一个对象。
多线程下:
在这里插入图片描述
但是结果是:
在这里插入图片描述
可以看出,多线程环境下,传统的懒汉式就出问题了。
解决方案1:加sync关键字。
在这里插入图片描述
就可以解决了,但是,性能差,对并发并不友好,强迫单车道执行。
解决方案2:DCL模式,即双端检查锁机制。
在这里插入图片描述
加锁的前后,都做一次判断。所谓的双重检查锁。
测试也是没有问题的。
在这里插入图片描述
到这,DCL版本的单例,还没有写完!为啥,因为刚才讲的,在多线程环境下,可能发生指令重排!!!!
在这里插入图片描述
在这里插入图片描述
所以,因为指令重排,会发生其他线程取值时候还没初始化完成的情况,就线程不安全了,没有满足instance的原子性。
所以需要volatile保证多线程之间的语义一致性。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值