《面试1v1》volatile
基本功
我是 javapub,一名 Markdown
程序员从👨💻,八股文种子选手。
面试官: 你能解释一下 volatile
关键字的作用吗?
候选人: 当我们在编写多线程程序时,经常会遇到线程安全的问题。其中一个常见的问题是可见性问题,即一个线程修改了共享变量的值,但是其他线程并不能立即看到这个修改。这时候,我们可以使用 volatile
关键字来解决这个问题。
面试官: 非常好。那么,你能具体说明一下 volatile
关键字是如何保证可见性的吗?
候选人: 当一个变量被声明为 volatile
后,每次访问这个变量时,都会从内存中读取最新的值,而不是使用 CPU 缓存中的旧值。同样地,每次修改这个变量时,都会立即将新值写入内存,而不是等到线程结束或者 CPU 缓存刷新时才写入。这样,其他线程就可以立即看到这个变量的最新值,从而保证了可见性。
在 JVM 中,volatile
关键字的实现涉及到以下几个方面:
- 内存屏障:JVM 会在
volatile
变量的读写操作前后插入内存屏障,以保证指令不会被重排序。内存屏障可以分为读屏障、写屏障和全屏障,分别用于保证读操作、写操作和所有操作的有序性。下面是 HotSpot JVM 中的volatile
内存屏障实现:
inline void OrderAccess::fence() {
__asm__ volatile ("" : : : "memory");
}
inline void OrderAccess::loadload() {
__asm__ volatile ("lfence" : : : "memory");
}
inline void OrderAccess::storestore() {
__asm__ volatile ("sfence" : : : "memory");
}
inline void OrderAccess::loadstore() {
__asm__ volatile ("mfence" : : : "memory");
}
inline void OrderAccess::storeload() {
__asm__ volatile ("mfence" : : : "memory");
}
- 内存语义:JVM 的内存模型规定了共享变量的访问方式,以及如何保证可见性和有序性。对于
volatile
变量,JVM 会保证每次读取都从内存中读取最新的值,每次写入都立即写入内存,以保证可见性和有序性。下面是 HotSpot JVM 中的volatile
内存语义实现:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest)
, "m" (*dest)
: "cc", "memory");
return exchange_value;
}
inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchg8b (%3)"
: "=A" (exchange_value)
: "b" ((jint)exchange_value), "c" ((jint)(exchange_value >> 32)), "r" (dest)
, "m" (*dest)
: "cc", "memory");
return exchange_value;
}
- 编译器优化:JVM 的编译器会对代码进行优化,以提高程序的性能。但是,对于
volatile
变量,编译器会禁止一些优化,以保证指令不会被重排序。比如,编译器不会将volatile
变量的读写操作与其他指令重排序,也不会将volatile
变量的读操作和写操作合并为一个操作。下面是 HotSpot JVM 中的volatile
变量读写操作的实现:
inline jint Atomic::load (volatile jint* p) { return *p; }
inline jlong Atomic::load (volatile jlong* p) { return *p; }
inline jfloat Atomic::load (volatile jfloat* p) { return *p; }
inline jdouble Atomic::load (volatile jdouble* p) { return *p; }
inline void Atomic::store (volatile jint* p, jint x) { *p = x; }
inline void Atomic::store (volatile jlong* p, jlong x) { *p = x; }
inline void Atomic::store (volatile jfloat* p, jfloat x) { *p = x; }
inline void Atomic::store (volatile jdouble* p, jdouble x) { *p = x; }
面试官: 很好。那么,你能否举一个例子来说明 volatile
关键字的作用呢?
候选人: 当然。比如,我们可以定义一个 flag
变量,并在一个线程中修改它的值,然后在另一个线程中读取它的值。如果 flag
变量没有被声明为 volatile
,那么在另一个线程中读取 flag
变量的值时,可能会看到旧值,而不是最新的值。但是,如果 flag
变量被声明为 volatile
,那么在另一个线程中读取 flag
变量的值时,就可以保证看到最新的值。
下面是一个简单的示例代码,演示了 volatile
关键字的作用:
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void doSomething() {
while (!flag) {
// do something
}
// do something else
}
}
在这个示例中,我们定义了一个 VolatileExample
类,其中包含一个 flag
变量。在 doSomething()
方法中,我们使用了一个 while
循环来等待 flag
变量的值变为 true
。如果 flag
变量没有被声明为 volatile
,那么在另一个线程中调用 setFlag(true)
方法后,doSomething()
方法可能会一直等待下去,因为它看不到 flag
变量的修改。但是,由于 flag
变量被声明为 volatile
,所以在另一个线程中调用 setFlag(true)
方法后,doSomething()
方法会立即看到 flag
变量的修改,从而退出循环。
面试官: 非常好。那么,你认为 volatile
关键字有什么缺点吗?
候选人: volatile
关键字只能保证可见性,不能保证原子性。如果一个变量的修改涉及到多个步骤,那么使用 volatile
关键字可能会导致线程安全问题。在这种情况下,我们需要使用其他的同步机制,比如 synchronized
关键字或者 Lock
接口。
面试官: 很好。你对 volatile
关键字的理解非常清晰。部分是比较考验工程师基本功的,你回答的很好,这部分可以过了。
候选人: 非常感谢。
最近我在更新《面试1v1》系列文章,主要以场景化的方式,讲解我们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣可以关注公众号JavaPub追更!
🎁目录合集:
Gitee:https://gitee.com/rodert/JavaPub
GitHub:https://github.com/Rodert/JavaPub
文章列表
📚最少必要面试题
- Java基础
- Java并发入门
- Java容器
- JavaWeb
- JVM
- MySQL
- MyBatis
- Spring
- SpringBoot
- Redis
- ElasticSearch
- [Kafka]
- Zookeeper
- Docker
- 缓存
📖知识点总结
下面是原创PDF干货版,持续更新中。
...
☕️Java基础
锁
jdk8
📝数据结构与算法
- 冒泡排序就是这么容易
- 选择排序就是这么容易
- 插入排序就是这么容易
- 希尔排序就是这么容易
- 归并排序就是这么容易
- 快速排序就是这么容易
- 堆排序就是这么容易
- 计数排序就是这么容易
- 桶排序就是这么容易
- 基数排序就是这么容易
📣Mybatis
🔬搜索
Lucene
Elasticsearch
- Springboot2.x整合ElasticSearch7.x实战目录
- Springboot2.x整合ElasticSearch7.x实战(一)
- Springboot2.x整合ElasticSearch7.x实战(二)
- Springboot2.x整合ElasticSearch7.x实战(三)
🎩Spring
Spring 学习路线图:
https://img-blog.csdnimg.cn/20201230220120483.png
Spring Boot
SpringBoot最新版常用案例整合,持续更新中 https://github.com/Rodert/SpringBoot-javapub
- SpringBoot快速入门-附源码
- Springboot项目的接口防刷
- SpringBoot 中的线程池,你真的会用么
- docker 打包 springboot 项目快速入门
- 自定义注解+AOP切面日志+源码
- SpringBoot2.x整合Prometheus+Grafana【附源码+视频】
💞中间件
zookeeper
RocketMQ
Prometheus
流程引擎
💍Redis
- rodert单排学习redis入门【黑铁】
- rodert 单排学习 redis 进阶【青铜】
- rodert单排学习redis进阶【白银一】
- rodert熬夜写了一份BloomFilter总结
- 了解Redis过期策略及实现原理
- 缓存:热点key重建优化
- 记一次redis线上问题
- 了解Redis过期策略及实现原理
📚Docker
📚sql
📚设计模式
🔒分布式
🌋shell
⚡️常用工具
Git
shell
linux
ffmpeg
实用工具
🌋加密
🔒GoLang
📚前端
💞区块链
🤖web实战
下载地址: github:https://github.com/Rodert/JavaPub-Web | gitee:https://gitee.com/rodert/JavaPub-Web
- SSM项目合集(公众号领取)
- 基于SSM图书馆管理系统
- 私活利器 时薪翻一番,推荐几个SpringBoot项目,建议你改改
- 16K点赞 基于Spring + Vue的前后端分离管理系统ELAdmin,真香
- Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台项目
- 一款基于 Spring Boot 的现代化社区(论坛/问答/社交网络/博客)
- 决定做一个开源项目
🚀实战面试
20212021 Java面试题系列教程
- Java基础--2021面试题系列教程--大白话解读--JavaPUb版本
- Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本
- Java反射--2021面试题系列教程--大白话解读--JavaPub版本
《面试1v1》Java面试八股文
《面试1v1》是我在面试中总结和推理出来的,准备在跳槽时温习回顾使用。
它采用对话的方式、口语化描述技术点,这里没有花费长篇大论的描述 API 怎么用,主要涉及到的都是高频面试题、及工作中如何使用,我还穿插了部分源码解析,因为现在面试中八股文必不可少,让文章由浅入深的更好理解。模拟了在真实面试场景中,候选人该如何回答。
迫不及待要看 面试1v1 全集怎么办? 目前在持续更新中,我一般会先更新到公众号,提催更
什么是《面试1v1》?
《面试1v1》是一个以对话形式讲解知识点的文章合集,是由 JavaPub 编写的真人1对1面试对话教程,通过真实案例编写,生动、有趣、干货满满。
为什么要写《面试1v1》这个专题?
我在后台收到很多读者的描述,说自己在面试准备过程中感觉抓不住重点,总是复习的没考、考的没复习。面试过后导致自己自信心受挫,不知道🤷♀️该看点什么来。
这里主要以我的经验给大家一个参照,我们如何在面试中自然的对答,不会因为紧张的忘记。如果用自己的话描述技术难题,避免背课文式的对话。
《面试1v1》有什么用?
文中大多是以实际面试中遇到的情况编写,几乎是大白话式的对话。涉及到的源码我也在对话中做了标注,方便我们查阅遗忘的知识点。
最终的目标是帮助大家更好的掌控面试,拿到心仪offer。
《面试1v1》收费吗,在哪里可以看到全集?
由 JavaPub 完全免费提供,并且持续更新中,在 wx 搜索 JavaPub 就可以直接查看全系列文章。
面试1v1 之后会出第二季吗?
会的,第二季会从大白话源码的角度出发,八股文的朋友不要错过。
【面试1v1】CountDownLatch-CyclicBarrier
原创电子书
链接:https://pan.baidu.com/s/1BUjGUevP00GqRw2b0HgBBA?pwd=6e67
提取码:6e67
看到这里了,点个关注呗!双击即可点赞!关注 @JavaPub