《隔离十四天》系列 -第九天 -各类面试题总结

前言

今天是第九天,但是已经开始可以自由活动了,我成为了一条狗,出去疯一样的撒欢。从来没被困过这么久啊!我只想大喊,劳自终于解放了!
在这里插入图片描述
在这里小弟也想询问看到这篇博文的各位大佬,腾讯外包的offer值不值得去,目前刚毕业想要学技术,但是看到网上说去腾讯外包学不到什么东西,所以在这里恳请给个建议。谢谢各位了!
在这里插入图片描述
好了言归正传,这几天一直在准备面试,最终也只是拿到了一个外包的offer,其它都没了信息,自认自己工作年限不行,以及简历的项目不行,只能多看看面试题,希望能找到一个心仪的公司。以下是自己整理的一些面试题,主要针对自己薄弱的地方,也希望能够帮到各位。

2、什么是线程安全?

 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果

3、 什么是共享变量的内存可见性问题?

共享变量存在于主存中,每个线程都有独立的内存空间,线程在操作数据时会把变量复制一份到缓存中,对缓存中的数据进行更改后再去同步到主
存中。

4、 什么是Java指令重排序?

 执行程序时为了提高性能,编译器和处理器常常会对指令做重排序,
 编译器优化的重排序---->指令级并行的重排序----->内存系统的重排序----->最终执行的指令序列

5、 讲讲ThreadLocal 的实现原理?

  ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。说白了,ThreadLocal就是想在多线程
  环境下去保证成员变量的安全。
 使用的Java中的弱引用。
ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象呢?这个时候ThreadLocal就失效了。仔细
观察下图中的代码,你会发现,threadLocal在初始化时返回的都是同一个对象!

6、 ThreadLocal 作为变量的线程隔离方式,其内部是如何做的?

set方法:线程Thread中有一个ThreadLocalMap对象, ThreadLocalMap是一个存在与ThreadLocal的静态内部类,同时包含一个Entry成员对
象(Entry也是一个静态内部类),从Entry的构造函数可以看出来,key=ThreadLocal,value是一个object对象(现在可以假想就是要设置的
值)。 如果ThreadLocalMap对象不存在,那么就new一个ThreadLocalMap,将ThreadLocal作为Entry对象的key,要设置的值作为Entry对
象的value,然后将ThreadLocalMap对象设为当前对象的threadLocals成员变量;
get方法:ThreadLocal能做到线程之间数据隔离的奥秘在于每个线程对象都有一个ThreadLocal.ThreadLocalMap对象,我们可以从宏观上简单
的理解为这就是一个Map(其实ThreadLocalMap里面有一个Entry数组,每一个Entry对象存储一个TheadLocal和一个value),Map的key都是
ThreadLocal对象,value就是每个线程设置的值,不同的线程是不一样的,这样就做到了线程之间的数据隔离的目的。
问题: Thread可能会产生OOM(内存泄露),ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手
动删除对应key就会导致内存泄漏,而不是因为弱引用。

7、 对AQS的理解?

 如果有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁
 的状态。初始状态下,这个state的值是0。
 这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。
 ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQS组件的。
 线程1跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。如果之前没人加过锁,
 那么state的值肯定是0,此时线程1就可以加锁成功。一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己

8、 讲讲独占锁 ReentrantLock 原理?

 ReentrantLock是基于AQS实现的,这在下面会讲到,AQS的基础又是CAS
AQS是基于FIFO队列的实现,因此必然存在一个个节点,Node就是一个节点,
 假设线程1调用了ReentrantLock的lock()方法,那么线程1将会独占锁,

9、 为什么需要代理模式?

1.分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
2.减少代码的重复,各个模块的重用性加强。
3.降低 模块间的耦合度,提高代码的可操作性和可维护性。
 定义一个抽象角色,让代理角色和真实角色分别去实现它。然后让真实角色去实现具体的业务逻辑,让代理对象实现具体业务逻辑前或后的一些
 事情,同时需要和真实角色说该你出场了(也就是需要在代理对象中传入真实对象,便于调用),最后实例化真实对象与代理对象,最后通过代理
 对象实现业务需求。
 首先实现InvocationHandler接口,然后通过java API创建代理类,最后动态加载代理类,代理类实现接口,使用handler
 (Proxy.newProxyInstance)以及调用invoke方法。

10、 讲讲静态代理模式的优点及其瓶颈?

 代理方式也是效率最高的一种方式,因为所有的类都是已经编写完成的,客户端只需要取得代理对象并且执行即可。
 静态代理虽然效率较高,但其也有不可避免的缺陷。可以看到,客户端在调用代理对象时,使用的是代理对象和被代理对象都实现的一个接口,
 我们可以将该接口理解为定义了某一种业务需求的实现规范。如果有另外一份业务需求(如进行数据修改),其与当前需求并行的,没有交集的,
 但是其在进行正常业务之外所做的安全验证工作与当前需求是一致的。

11、动态代理的理解?

 jdk代理指的是借助jdk所提供的相关类来实现代理模式,其主要有两个类:InvocationHandler和Proxy。在实现代理模式时,只需要实现
 InvocationHandler接口即可。
  jdk代理解决了静态代理需要为每个业务接口创建一个代理类的问题,虽然使用反射创建代理对象效率比静态代理稍低,但其在现代高速jvm中
  也是可以接受的,在Spring的AOP代理中默认就是使用的jdk代理实现的。这里jdk代理的限制也是比较明显的,即其需要被代理的对象必须实现
  一个接口。这里如果被代理对象没有实现任何接口,或者被代理的业务方法没有相应的接口,我们则可以使用另一种方式来实现,即Cglib代理。
 Cglib代理是功能最为强大的一种代理方式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了jdk代理需要被代理对象实现某个
 接口的问题。对于需要代理的类,如果能为其创建一个子类,并且在子类中编写相关的代理逻辑,因为“子类 instanceof 父类”,因而在进行调
 用时直接调用子类对象的实例,也可以达到代理的效果。Cglib代理的原理实际上是动态生成被代理类的子类字节码,由于其字节码都是按照jvm
 编译后的class文件的规范编写的,因而其可以被jvm正常加载并运行。这也就是Cglib代理为什么不需要为每个被代理类编写代理逻辑的原因。

12、什么是反射机制?

 在运行状态中,对于任意的一个类,都能够知道这个类的所有属性和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态
 获取类信息及动态调用类对象方法的功能称为java的反射机制
 反射作用总结就是:1.动态地创建类的实例,将类绑定到现有的对象中,或从现有的对象中获取类型。2.应用程序需要在运行时从某个特定的程序
 集中载入一个特定的类。

13、什么是代理模式?

 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象
 可以在客户端和目标对象之间起到中介的作用。而代理模式又分为静态代理和动态代理,先说静态代理。
 静态代理通俗点将就是自己手写一个代理类,而动态代理则不用我们手写,而是依赖于java反射机制
 动态代理,必须先实现这个InvocationHandler接口,方法里面有一个Proxy类,这个Proxy类提供了很多方法,这里我们用的是
 newProxyInstance方法,它有三个参数,第一个是被代理类的类构造器,第二个指的是被代理类的接口,也就是Subject接口,第三个是实现
 这个代理的类,这里就是本类。

14、cglib动态代理?

 CGLib是一个强大的, 高性能的代码生成库.  被广泛应用于 AOP 框架. 用以提供方法拦截操作.
CGLib采用底层的字节码技术,  可以为一个类创建子类,  在子类中采用方法拦截的技术拦截所有父类方法的调用,  并织入横切逻辑.
  代理对象的生成由 Enhancer 类实现. Enhancer是CGLib的字节码增强器. 可以很方便的对类进行扩展.
  Enhancer 创建代理对象的大概步骤如下:
   1. 生成代理类## 标题 Class 二进制字节码.
    2.通过 Class.forname() 加载字节码文件, 生成 Class 对象.
    3.通过反射机制获得实例构造, 并创建代理类对象.

15、jdk动态代理和cglib代理的特点?

  JDK动态代理:  (1)代理类继承 Proxy 类, 并且实现委托类接口, 主要通过代理类调用 InvocationHandler 实现类的重写方法 invoke() 
    来实现动态代理. 

(2)只能对接口进行代理. (只能对实现接口的委托类进行代理)

(3)底层使用反射机制进行方法掉调用.

CGLib动态代理:  (1)代理类继承了委托类, 在代理方法中, 会判断是否存在实现了 MethodInterceptor 接口的对象, 若存在则调用对象的 
invoke() 方法, 对委托方法进行代理. 

(2)不能对 final 类以及 final , private方法进行代理.

(3)底层讲方法全部放入一个数组中, 通过索引直接进行方法调用.

16、SpringIOC如何解决循环依赖?

 三级缓存解决了Bean之间的循环依赖。当一个Bean调用构造函数进行实例化后,即使属性还未填充,就可以通过三级缓存向外暴露依赖的引用值
 (所以循环依赖问题的解决也是基于Java的引用传递),这也说明了另外一点,基于构造函数的注入,如果有循环依赖,Spring是不能够解决的。
 还要说明一点,Spring默认的Bean Scope是单例的,而三级缓存中都包含singleton,可见是对于单例Bean之间的循环依赖的解决,Spring是
 通过三级缓存来实现的。

17、 对比一下B+树索引和 Hash索引?

   在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速
   左右移动,效率非常高。
     BTree索引是最常用的mysql数据库索引算法,因为它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作
     符,只要它的查询条件是一个不以通配符开头的常量
     Hash索引仅仅能满足"=",“IN"和”<=>"查询,不能使用范围查询。 由于 Hash 索引比较的是进行 Hash 运算之后的 Hash值,所以它只能
     用于等值的过滤,不能用于基于范围的过滤,因为经 过相应的 Hash算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全
     一样。
    
Hash 索引无法被用来避免数据的排序操作。
由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash值,而且Hash值的大小关系并不一定和 Hash运算前的键值完全一样,所以数据库无法
利用索引的数据来避免任何排序运算;
Hash索引不能利用部分索引键查询。
对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash值,所以通过组合索引的前面
一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
Hash索引在任何时候都不能避免表扫描。
前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash值和所对应的行指针信息存放于一个 Hash 表中,由于不同
索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash索引中直接完成查询,还是要通过访问表中的实际
数据进行相应的比较,并得到相应的结果。
Hash索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。


在MySQL中,只有HEAP/MEMORY引擎表才能显式支持哈希索引(NDB也支持,但这个不常用),InnoDB引擎的自适应哈希索引(adaptive
hash index)不在此列,因为这不是创建索引时可指定的。
还需要注意到:HEAP/MEMORY引擎表在mysql实例重启后,数据会丢失。
通常,B+树索引结构适用于绝大多数场景,像下面这种场景用哈希索引才更有优势:
在HEAP表中,如果存储的数据重复度很低(也就是说基数很大),对该列数据以等值查询为主,没有范围查询、没有排序的时候,特别适合采用哈
希索引

以上是自己看面试题整理下来的,答案是找的网上的答案,如果有错误的地方希望能给与指正,谢谢。最后希望各位都能找到心仪的工作。

发布了92 篇原创文章 · 获赞 7 · 访问量 7538

猜你喜欢

转载自blog.csdn.net/qq_40126996/article/details/104417980