2020年JAVA面试208题

小崔爱读书

面试是类似演讲演说的过程,网上的面试资料都是文字的,没办法让面试者直观的体验到面试过程,希望这个音频节目对你有帮助。纯粹硬核IT知识,技术小白请绕路。搜集整理各种面试官提出的技术问题。为IT人提供专业知识的面试指导。

  • 24 minutes 52 seconds
    synchronized的底层原理
    17 October 2022, 12:51 am
  • 8 minutes 4 seconds
    多线程原子性可见性和有序性的理解之三
    27 September 2022, 11:30 pm
  • 8 minutes 11 seconds
    多线程原子性可见性和有序性的理解之二
    26 September 2022, 11:30 pm
  • 4 minutes 35 seconds
    多线程原子性可见性和有序性的理解之一


    说到多线程的原子性、可见性和有序性。

    这是多线程确保线程安全的三个标准。

    首先。咱说说。原子性。原子性其实很好理解。原子就是最小的单元,他就是可执行的最小的单元。在程序执行的时候,最小的一个可执行单元就是一个原子。

    一段原子性的代码执行的时候。不会被打断。这一段代码的执行,要么不执行,要么全部执行完毕。

    这段代码也许只有一行代码,也许是多行代码。

    一行代码很多也不是原子性的,因为这个原子性并非是我们Java程序代码的原子性,而是CPU执行 阶段的原子性。并不是说代码少他就是原子性。也不是说代码多的就不是原子性。一行代码很多都不是原子性的,多行代码加上锁,也可以是原子性的。

    比如。定义一个整型int i = 10。这一行代码它就是原子性的,这里是给整形变量i赋值,这个就是原子性的,它不可能被打断。

    如果定义的一个长整型的 long j=10,这就不是原子性的,为什么呢?因为这个long类型啊,他是64位的,64位的长整型。在CPU中执行赋值操作的时候,它是分两步,分别64位的高位和低位进行赋值,这是分两步来完成的。类似的double也是这样子,double也是64位的,会分两步分别给高32位和低32位赋值,就是两步操作,不是原子性的。

    那么咱们来看另一个语句。int i=10; i++ ; 这个i=10我们刚刚说过了,是原子性的,那这个 i++是不是原子型的呢?直接说答案:i++不是原子型的。

    别看这么简单的一个计算,写程序就一行,但到了CPU级别就需要3条指令,1 获取i的值, 2 执行i+1 的操作,3 把i+1的结果赋值给i 。所以这个i++不是原子性的。

    这么简单的语句都不是原子性的,那么是不是对于多行代码就肯定不是原子性了呢?其实也不是,Java可以利用锁来保证多行语句是原子性的。

    从CPU的角度来看,就是插入一个Lock指令。当一段代码被Lock指令锁住后,一个线程执行这段代码的时候,其他线程就无法执行,只能等着这个线程执行完毕才能获得执行权。这就是保证这段代码的原子性。

    具体在Java代码里面写的时候就用synchronized 和lock对象,来实现一段代码,一个方法,一个对象的锁。这是对原子性的解释。


    那么可见性是什么呢?在Java的内存模型中,每一个子线程会拥有一个单独的工作内存,主线程有一个主内存。主内存中存放的是共享变量,虽说是共享变量,看起来是被主线程和子线程共享的,那么子线程就可以读写操作该共享变量了吧?其实不行的,子线程是不能直接读写操作主内存中的共享变量的。

    子线程会在工作内存中创建该共享变量的副本,然后读写操作该副本。这里再强调一下,子线程不能直接读写主内存的共享变量,只能读写工作内存中的共享变量的副本,读写完毕后再将副本的值回写到主内存中。

    这时候,我们就发现一个问题了,假设主线程中有共享变量 x = 10 , 有两个子线程 A和B,要操作共享变量x,就会分别在各自的工作内存中加载一份x的副本。这时候咱们看到了,原来只有一个x,现在变成了3份:主内存中的x和两个副本x。

    如果子线程A对副本x做了+1 操作,线程A中的x就变成 11 了,但线程B看到的x还是10,这就出现了可见性的问题。

    通过上面的描述,总结一下什么是线程的可见性问题:就是多线程情况下,一个线程修改了变量的值,另一个线程看不到这个变化。

    那怎么解决这个问题呢?思路是这样的,对于线程A来说,只要修改了x副本的值,马上把这个值回写到主内存中;对于线程B来说,只要想读x的值,就再次从主线程加载x,这样副本的值永远都与主内存中的值是一样的。这就可以解决可见性问题了。

    在具体实现上Java内存模型是通过内存屏障来实现的。

    那这里又引出一个内存屏障的概念,啥是内存屏障呢?内存屏障其实就是一个指令,将这个指令插入到其他指令之间,会执行某些特殊的操作。具体到解决可见性是两个内存屏障指令:load屏障和store屏障。 load和store这俩词儿之前讲内存模型的8个指令的时候见过,load指令是工作内存从主内存加载共享变量生成副本的指令,store指令是将工作内存的共享变量副本回写到主内存的指令。那么load屏障就呼之欲出了,这是在指令A前插入load屏障,那么指令A用到的变量副本将失效,必须从主内存加载对应的共享变量的值并替换当前副本;对应的store屏障插入到指令B之后,那么指令B修改的副本的值,马上会被回写到主内存。

    这个效果应该可以想象了吧?通过load屏障和store屏障,可以实现修改了副本马上回写到主内存,读副本之前先从主内存加载一下,这样就确保了,只要副本值修改了马上就更新到主内存里面,然后另一个线程读副本的时候直接装载主内存中的新值,这样就解决了多线程下变量的可见性。


    接下来讲解一下有序性。

    什么是有序性呢?这里的序是指什么的顺序呢?

    这里说的序是指CPU中指令执行的顺序,就是一条条的汇编指令的先后顺序。我们Java程序员写的是Java代码,写出来一行行的Java代码最后编译转为汇编在CPU中执行,这时候就是一条条的汇编指令了。一般程序员的理解是这样的,写了两行代码,行A在行B的前面,应该是先执行A再执行B,但其实CPU在指令执行的时候做了优化,如果指令A和指令B调换顺序后不影响执行结果,那么CPU会进行指令重排,重排后就是先执行指令B再执行指令A。这个原则叫做as if serial ,就是好像是连续的一样,也就是重排序优化后就好像没有重排序,而是正常连续执行的那样。

    比如:

    int i=10;

    int j=20;

    这样的两条指令,先执行哪条其实是无所谓的,这时候CPU就会执行指令重排。

    对于单线程程序来说,CPU指令重排绝对不会有问题,但对于多线程指令重排就有可能产生数据安全问题。

    打个比喻:

    刘能和赵四去买冰糕,这个指令顺序是,推门进冷饮店,掏钱,拿冰糕。

    如果就刘能自己去买冰糕的话,先掏钱再拿冰糕还是先拿冰糕再掏钱,没什么关系,冷饮店老板照顾的过来。

    现在刘能和赵四俩人一起去,结果赵四掏钱了,还没拿冰糕,刘能先执行了拿冰糕,这下赵四不干了,我掏的钱,我掏的钱。老板说,没办法,人太多忙不过来,先后顺序调整一下,这样快,你别叫唤了。

    那赵四能干嘛?赵四大叫:不行啊,出现多线程的有序性问题了。


    那咋解决这个问题呢?还是下内存屏障。

    有序内存屏障是在两条指令之间插入的屏障,插入后,前后两条指令就不能重排序了。

    处理有序性的屏障有4个。

    LoadLoad屏障:下在load和load之间,这个屏障指令之前的load指令和之后的load相关的指令不能重排序;

    LoadStore屏障:下在load和store之间,这个屏障指令之前的load指令和之后的store指令不能重排序;

    StoreLoad屏障:下在store和load之间,这个屏障指令之前的store指令和之后的load指令不能重排序;

    StoreStore屏障:下载store和store之间,这个屏障指令之前的store指令和之后的store指令不能重排序。

    说白了,这个有序性的内存屏障,就是禁止CPU对前后的指令进行重排序的,有了这个屏障,多线程下CPU也不能做重排序优化了,就解决了有序性的问题了。


    关于内存屏障的细节,我们今儿就不聊了,以后我们再细说。

    这个多线程的原子性、可见性和有序性是啥意思,你明白了吧?









    26 September 2022, 12:07 am
  • 8 minutes 22 seconds
    JAVA的内存模型
    18 September 2022, 11:30 pm
  • 2 minutes 47 seconds
    CPU的多级缓存模型
    15 September 2022, 11:30 pm
  • 6 minutes 55 seconds
    038_ThreadLocal的底层原理
    13 September 2022, 11:44 pm
  • 5 minutes 20 seconds
    Java中的弱引用是什么


    1 Java中有4中引用,强引用、软引用、弱引用、虚引用。

    强引用:普通new出来一个对象 ,都是强引用。

    软引用:SoftReference类,当内存不足的时候,会被回收。

    弱引用:WeakReference类,内存充足也会被垃圾回收。

    虚引用:最脆弱的引用,记录一个对象已经被回收了。


    2 当某个对象只被弱引用的时候,Java的垃圾回收机制就会回收该对象。

    3 我们知道一个对象定义出来,在堆内存中为其开辟空间,在栈中存储该对象的引用。比如定义一个Car a1 = new Car() ; 这时候在堆中开辟了一块空间存储Car的数据,而a1则存储在栈中,引用堆中的Car。

    4 如果我们设置 a1 = null,那么堆中的Car空间就不被栈引用了,Java的垃圾回收就会回收这块内存。

    5 如果我们设置 a1 = new Car() ; a2 = a1 ; a1 = null ; 这时候虽然 a1 不引用堆中的Car,但a2 还引用着堆中的Car,那么堆中的Car空间是不会被回收的。

    6 这时候就可以使用弱引用了。 a1 = new Car() ; a2 = new WeakReference(a1) ; a1 = null。

    这时候 a1 不再引用堆中的Car空间,a2 是一个弱引用,虽然引用着堆中的Car空间,由于堆中的Car空间只被弱引用,因此Java的垃圾回收就会回收该空间。







    8 September 2022, 11:30 pm
  • 1 minute 56 seconds
    033_Spring的Bean是否线程安全的
    7 September 2022, 11:30 pm
  • 3 minutes 12 seconds
    032_Spring中Bean的生命周期


    1 准备Spring的上下文环境,也就是ApplicationContext

    2 扫描XML文件,或者是注解,得到一系列的BeanDefinitaion

    3 BeanFactoryPostProcessor,Bean工厂的后置处理器, 要对BeanDefinition做一些处理,替换一些属性的值,比如MyBatis的Bean,就会做这个处理。

    4 开始实例化Bean,即new出来Java对象,当然,他不是真的new,而是通过反射实例化Bean

    5 开始初始化Bean,对Bean的属性赋值,即根据Bean的依赖关键,进行Bean的依赖注入

    6 对Bean的后置处理,调用BeanPostProcessor,进行AOP相关的操作,这时候就会对Bean进行切面增强。

    7 将Bean放入Bean容器,即一个HashMap中,这时候开发者就可以使用这个Bean了。

    8 Bean的销毁,当Spring的AppliationContext要关闭的时候,会调用DiposibleBean的destory方法,或者Bean的destory方法,进行Bean销毁。









    6 September 2022, 11:30 pm
  • 2 minutes 27 seconds
    AOP的底层原理


    1 AOP就是面向切面编程,通过切面来达到对目标类的增强的目的,关于面向切面就不做太多的解释了。

    2 AOP底层是动态代理。

    3 AOP针对实现了接口的类,基于原生JDK的动态代理实现的; 针对没有实现接口的类,基于CGLib来实现的。

    4 CGLIb的动态代理,通过实现一个子类来实现的。

    5 JDK的动态代理有一个限制,只能针对实现了接口的类进行动态代理,这是为什么呢?因为JDK的动态代理实现的代理类不是继承目标类,而是继承自Proxy类,而JAVA不允许双重代理,因此就要求目标类必须实现了接口的类。





    5 September 2022, 11:30 pm
  • More Episodes? Get the App
About 2020年JAVA面试208题
© MoonFM 2024. All rights reserved.