《面试1v1》volatile

基本功

我是 javapub,一名 Markdown 程序员从👨‍💻,八股文种子选手。

面试官: 你能解释一下 volatile 关键字的作用吗?

候选人: 当我们在编写多线程程序时,经常会遇到线程安全的问题。其中一个常见的问题是可见性问题,即一个线程修改了共享变量的值,但是其他线程并不能立即看到这个修改。这时候,我们可以使用 volatile 关键字来解决这个问题。

面试官: 非常好。那么,你能具体说明一下 volatile 关键字是如何保证可见性的吗?

候选人: 当一个变量被声明为 volatile 后,每次访问这个变量时,都会从内存中读取最新的值,而不是使用 CPU 缓存中的旧值。同样地,每次修改这个变量时,都会立即将新值写入内存,而不是等到线程结束或者 CPU 缓存刷新时才写入。这样,其他线程就可以立即看到这个变量的最新值,从而保证了可见性。

在 JVM 中,volatile 关键字的实现涉及到以下几个方面:

  1. 内存屏障: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");
}
  1. 内存语义: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;
}
  1. 编译器优化: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

http://javapub.net.cn

文章列表

📚最少必要面试题

📖知识点总结

下面是原创PDF干货版,持续更新中。

...

☕️Java基础

jdk8

📝数据结构与算法

  1. 冒泡排序就是这么容易
  2. 选择排序就是这么容易
  3. 插入排序就是这么容易
  4. 希尔排序就是这么容易
  5. 归并排序就是这么容易
  6. 快速排序就是这么容易
  7. 堆排序就是这么容易
  8. 计数排序就是这么容易
  9. 桶排序就是这么容易
  10. 基数排序就是这么容易

📣Mybatis

🔬搜索

Lucene

Elasticsearch

🎩Spring

Spring 学习路线图:
https://img-blog.csdnimg.cn/20201230220120483.png

Spring Boot

SpringBoot最新版常用案例整合,持续更新中 https://github.com/Rodert/SpringBoot-javapub

💞中间件

zookeeper

RocketMQ

Prometheus

流程引擎

💍Redis

📚Docker

📚sql

📚设计模式

🔒分布式

🌋shell

⚡️常用工具

Git

shell

linux

ffmpeg

实用工具

🌋加密

🔒GoLang

📚前端

💞区块链

🤖web实战

下载地址: github:https://github.com/Rodert/JavaPub-Web | gitee:https://gitee.com/rodert/JavaPub-Web

🚀实战面试

20212021 Java面试题系列教程

《面试1v1》Java面试八股文

《面试1v1》是我在面试中总结和推理出来的,准备在跳槽时温习回顾使用。

它采用对话的方式、口语化描述技术点,这里没有花费长篇大论的描述 API 怎么用,主要涉及到的都是高频面试题、及工作中如何使用,我还穿插了部分源码解析,因为现在面试中八股文必不可少,让文章由浅入深的更好理解。模拟了在真实面试场景中,候选人该如何回答。

迫不及待要看 面试1v1 全集怎么办? 目前在持续更新中,我一般会先更新到公众号,提催更

什么是《面试1v1》?

《面试1v1》是一个以对话形式讲解知识点的文章合集,是由 JavaPub 编写的真人1对1面试对话教程,通过真实案例编写,生动、有趣、干货满满。

为什么要写《面试1v1》这个专题?

我在后台收到很多读者的描述,说自己在面试准备过程中感觉抓不住重点,总是复习的没考、考的没复习。面试过后导致自己自信心受挫,不知道🤷‍♀️该看点什么来。

这里主要以我的经验给大家一个参照,我们如何在面试中自然的对答,不会因为紧张的忘记。如果用自己的话描述技术难题,避免背课文式的对话。

《面试1v1》有什么用?

文中大多是以实际面试中遇到的情况编写,几乎是大白话式的对话。涉及到的源码我也在对话中做了标注,方便我们查阅遗忘的知识点。

最终的目标是帮助大家更好的掌控面试,拿到心仪offer。

《面试1v1》收费吗,在哪里可以看到全集?

由 JavaPub 完全免费提供,并且持续更新中,在 wx 搜索 JavaPub 就可以直接查看全系列文章。

面试1v1 之后会出第二季吗?

会的,第二季会从大白话源码的角度出发,八股文的朋友不要错过。

【面试1v1】hashmap

【面试1v1】java注解

【面试1v1】java泛型

【面试1v1】java多线程

【面试1v1】CAS

【面试1v1】java反射

【面试1v1】动态代理

【面试1v1】javaNIO

【面试1v1】synchronized

【面试1v1】volatile

【面试1v1】线程池

【面试1v1】ThreadLocal

【面试1v1】JVM内存模型

【面试1v1】CountDownLatch-CyclicBarrier

【面试1v1】JVM类加载过程

【面试1v1】垃圾回收机制

原创电子书
链接:https://pan.baidu.com/s/1BUjGUevP00GqRw2b0HgBBA?pwd=6e67
提取码:6e67

看到这里了,点个关注呗!双击即可点赞!关注 @JavaPub

posted @ 2023-06-07 20:58  JavaPub  阅读(9)  评论(0编辑  收藏  举报