文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

面试官:有了解过Volatile关键字吗 说说看

2024-12-13 21:53

关注

概念回顾

首先我们回顾一下之前讲的基本概念:

内存可见性

「内存可见性,指的是线程之间的可见性,当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值」

重排序

为优化程序性能,对原有的指令执行顺序进行优化重新排序。重排序可能发生在多个阶段,比如编译重排序、CPU重排序等。

happens-before

遵循happens-before规则,JVM就能保证指令在多线程之间的顺序性符合执行的预期。

volatile

那么这个内存可见性过程是怎么样的呢之前也有给大家演示过具体代码,这里直接给大家总结一下:

所谓内存可见性, 当一个线程对volatile修饰的变量进行写操作时,会立即将本地内存中的共享变量刷新到主内存, 同理,当进行读操作时,会立即将本地内存失效,从主内存中读取共享变量的值。

在这一点上,volatile与锁具有相同的内存效果,volatile变量的写和锁的释放具有相同的内存语义,volatile变量的读和锁的获取具有相同的内存语义。

禁止重排又是怎么回事呢?

在JSR-133之前的旧的Java内存模型中,是允许volatile变量与普通变量重排序的。想想看,如果可重排,会发生什么?

我们假设有两个线程A和B,一个被volatile修饰的变量a,一个未被修饰的普通变量b,看下边代码:

volatile a = 1;
int b = 2;
public void writer() {
a = 1;
b = 3;
}
public void reader() {
if (a == 1) {
System.out.println(b);
}
}

线程A执行writer方法,首先将a设置为1,此时B线程操作reader方法,此时判断a=1,然后进行输出b=2,线程A多b操作设置为3,其实最终结果应该b=3才对,所以这里重排可能会导致普通变量读错的情况。

为了提供一种比锁更轻量级的「线程间的通信机制」,JSR-133决定增强volatile的内存语义:严格限制编译器和处理器对volatile变量与普通变量的重排序。那么它是怎么禁止的呢❓答案是通过内存屏障,或许你听说过这个概念,下面我们一起看一下。

内存屏障

什么是内存屏障呢?在计算机中,主要分为两种,一种是读屏障(Load Barrier)和写屏障(Store Barrier)。内存屏障有两个作用:

我们写的代码最终都是要通过编译器的,那么编译器是怎么实现这个过程的呢?

编译器在生成字节码的时候,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。在Java中,JMM内存屏障插入策略可以保证各平台处理器下程序的volatile内存语义正确,具体策略:

volatile与普通变量的重排序规则:

  1. 如果第一个操作是volatile读,那无论第二个操作是什么,都不能重排序。
  2. 如果第二个操作是volatile写,那无论第一个操作是什么,都不能重排序。
  3. 如果第一个操作是volatile写,第二个操作是volatile读,那不能重排序。

那么如果第一个操作是普通变量读,第二个是volatile读,可以重排吗?

答案: 可以的。

volatile使用场景

相信在了解以上概念之后,对它应该有一定的认识了, volatile可以保证内存可见性且禁止重排序, 它跟锁又具有相同的内存语义,又被称为轻量级锁。volatile仅仅保证对单个volatile变量的读/写具有原子性,而锁可以保证整个「临界区代码」的执行具有原子性。所以锁更高级一点。但也不是说volatile就不好,作为轻量级的锁,某些场景下还是非常有用的。

我们以双重锁检查单例模式为例,首先我们看一下普通的单例模式:

class Singleton {
private static Singleton instance;
private Singleton() {}
public Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}

单线程下你可以这么搞,没毛病,多线程下就不行了,所有我们要加锁,于是双重锁检查下的实现:

class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

这样就真没问题了吗疑问?我们知道在new的时候,主要做了三件事:

这个过程中,可能会导致指令重排,有可能你会说里边加锁了,上节给大家介绍顺序一致性模型中,我们讲过,在同步模式下临界区内的代码可以发生重排序,所以这里还是有可能发生重排序的,所以最终的这个过程,可能会这样。

线程A执行 分配内存 -> 变量赋值, 线程B执行 判断 instance不为null 开始访问对象,实际上对象还未初始化,所以这时候,我们就要加上volatile。

class Singleton {
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;
}
}

这样在多线程环境下,可以保证其安全性。

结束语

本节内容可能不像之前那么好理解,比较抽象,所以本文也有不足的地方,大家自己可以多查查一些资料,综合理解, 不要去背概念。本节我们提到了锁的概念。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯