Offer快到碗里来,Volatile问题终结者

Hello,大家好,我是只会写HelloWorld的程序员大黄。

Java中并发编程是各个大厂面试重点,很多知识点晦涩难懂,常常需要结合实际经验才能回答好,面试没有回答好,则容易被面试官直接挂掉。

因此,大黄利用周末时间,呕心沥血,整理之前和面试官battle的面试题目。

由于并发变成问题实在是太多了,一篇文章不足以囊括所有的并发知识点,打算分为多篇来分析,面试中的并发问题该如何回答。本篇主要围绕volatile关键字展开。

关于并发编程一些源码和深层次的分析已经不胜枚举,大黄不打算从各方面展开,只希望能够借用这篇文章沟通面试中该如何回答,毕竟面试时间短,回答重点、要点才是关键。

面试问题概览

下面我罗列一些大厂面试中,关于并发编程常见的一些面试题目,有的是自己亲身经历,有的是寻找网友的面经分享。

可以先看看这些面试题目,现在心中想想,如果你面对这些题目,该如何回答呢?

  1. volatile关键字解释一下【字节跳动】
  2. volatile有啥作用,如何使用的呢【京东】
  3. synchronized 和volatile 关键字的区别【京东】
  4. volatile原理详细解释【阿里云】
  5. volatile关键字介绍,内存模型说一下【滴滴】
  6. Volatile底层原理,使用场景【抖音】

可以看到volatile关键字在各个大厂面试中已经成为了必考的面试题目。回答好了必然称为加分项,回答不好嘿嘿,你懂的。

面试回顾

一个身着灰色格子衬衫,拿着闪着硕大的logo小哥迎面走来,我心想,logo还自带发光的,这尼玛肯定是p7大佬了,但是刚开始咱们还是得淡定不是。

面试官:大黄同学是吧,我看你简历上面写能够熟练掌握并发编程核心知识,那我们先来看看并发编程的一些核心知识吧。有听过volatile吗?说说你对于这个的理解。

记住:此时还是要从为什么、是什么、有什么作用回答,只有这样才能给面试官留下深刻印象。

大黄:面试官您好,volatile是java虚拟机提供的轻量级同步机制,主要特点有三个:

  1. 保证线程之间的可见性
  2. 禁止指令重排
  3. 但是不保证原子性
面试中,肯定不是说完这三点就完了,一般需要展开来说。

大黄:所谓可见性,是多线程中独有的。A线程修改值之后,B线程能够知道参数已经修改了,这就是线程间的可见性。 A修改共享变量i之后,B马上可以感知到该变量的修改。

面试官可能会追问,为什么会出现变量可见性问题了。这个就涉及到Java的内存模型了(俗称JMM),因此你需要简单说说Java的内存模型。
面试官:那为什么会出现变量可见性问题呢?

大黄:JVM运行程序的实体都是线程,每次创建线程的时候,JVM都会给线程创建属于自己的工作内存,注意工作内存是该线程独有的,也就说别的线程无法访问工作内存中的信息。而Java内存模型中规定所有的变量都存储在主内存中,主内存是多个线程共享的区域,线程对变量的操作(读写)必须在工作内存中进行。

面试中记得不要干说理论,结合一下例子,让面试官感到你真的掌握了。上面的问题你抓住主内存、线程内存分别阐述即可。

大黄:比如,存在两个线程A、B,同时从主线程中获取一个对象(i = 25),某一刻,A、B的工作线程中i都是25,A效率比较高,片刻,改完之后,马上将i更新到了主内存,但是此时B是完全没有办法i发生了变化,仍然用i做一些操作。问题就发生了,B线程没有办法马上感知到变量的变化!!

大黄可见性Demo演示小插曲

import lombok.Data;
/**
 * @author dahuang
 * @time 2020/3/15 17:14
 * @Description JMM原子性模拟
 */
public class Juc002VolatileAtomic {
    public static void main(String[] args) {
        AtomicResource resource = new AtomicResource();
        // 利用for循环创建20个线程,每个线程自增100次
 for(int i = 0; i < 20; i++){
 new Thread(()->{
 for (int j = 0; j < 100; j++) {
 resource.addNum();
 }
 },String.valueOf(i)).start();
 }
 // 用该方法判断上述20线程是否计算完毕,
 // 如果小于2,则说明计算线程没有计算完,则主线程暂时让出执行时间
 while (Thread.activeCount() > 2){
 Thread.yield();
 }
 // 查看number是否可以保证原子性,如果可以保证则输出的值则为2000
 System.out.println("Result = "+resource.getNumber());
 }
}
@Data
class AtomicResource{
 volatile int number = 0;
 public void addNum(){
 number++;
 }
}

下面是运行结果:
结果如下:

Result = 1906

Process finished with exit code 0

面试官:volatile可以保证程序的原子性吗?
大黄:JMM的目的是解决原子性,但volatile不保证原子性。为什么无法保证原子性呢?
因为上述的Java的内存模型的存在,修改一个i的值并不是一步操作,过程可以分为三步:

  1. 从主内存中读取值,加载到工作内存
  2. 工作内存中对i进行自增
  3. 自增完成之后再写回主内存。

每个线程获取主内存中的值修改,然后再写回主内存,多个线程执行的时候,存在很多情况的写值的覆盖。

大黄可见性Demo演示小插曲

用下面的例子测试volatile是否保证原子性。

import lombok.Data;
/**
 * @author dahuang
 * @time 2020/3/15 17:14
 * @Description JMM原子性模拟
 */
public class Juc002VolatileAtomic {
    public static void main(String[] args) {
        AtomicResource resource = new AtomicResource();
        // 利用for循环创建20个线程,每个线程自增100次
 for(int i = 0; i < 20; i++){
 new Thread(()->{
 for (int j = 0; j < 100; j++) {
 resource.addNum();
 }
 },String.valueOf(i)).start();
 }
 // 用该方法判断上述20线程是否计算完毕,如果小于2,
 // 则说明计算线程没有计算完,则主线程暂时让出执行时间
 while (Thread.activeCount() > 2){
 Thread.yield();
 }
 // 查看number是否可以保证原子性,如果可以保证则输出的值则为2000
 System.out.println("Result = "+resource.getNumber());
 }
}
@Data
class AtomicResource{
 volatile int number = 0;
 public void addNum(){
 number++;
 }
}

结果如下:

Result = 1906
可以看到程序循环了2000次,但是最后值却只累加到1906,说明程序中有很多覆盖的。

面试官可能心想,好家伙,懂得还挺多,我来试试你的深浅。

面试官:那如果程序中想要保证原子性怎么办呢?
大黄:Juc(Java并发包简称)下面提供了多种方式,比较轻量级的有Atomic类的变量,更重量级有Synchronized关键字修饰,前者的效率本身是后者高,不用加锁就可以保证原子性。

你可能感兴趣的:(css3)