【面经题解1】快手Java开发-面经解析

背景&叠甲

面经地址:

  1. https://www.nowcoder.com/feed/main/detail/d9e470c708ee4c99a0239e17f990caa9

目的:复习八股文、反复记忆
范围:基础、多线程、集合、MySQL、Redis、Spring全家桶、Mybatis、RocketMQ、微服务、分布式等
没有JVM,操作系统,网络,算法,问就是没梦想去大厂,卷卷中小厂得了。
-------------------------------------叠甲-------------------------
Work-Life Balance,虽然是个伪命题,但仍是我向往的。

  • 如果有不对的地方,欢迎指正
  • 肯定有疏忽的地方,希望指正
  • 原作者不希望传播或引用,可联系删除

-------------------------------------叠甲-------------------------

正文

  1. String的特性,不可变的好处,怎么实现不可变的

String是Java中的一个类,用于表示字符串。它的特性之一就是不可变性,也就是说,一旦创建了一个String对象,就无法修改它的值。
不可变性的好处有以下几点:

  1. 线程安全:由于String对象是不可变的,多个线程可以同时访问同一个String对象而无需担心数据的修改问题,这样就避免了线程安全的隐患。
  2. 缓存哈希值:String类重写了hashCode()方法,并且在第一次计算哈希值时将其缓存起来。由于不可变性的特性,可以确保哈希值的一致性,使得String对象可以被安全地用作Map的键。
  3. 安全性:在一些安全敏感的场景中,不可变性可以防止对象的状态被修改,从而提供更高的安全性。

实现不可变的方式有以下几种:

  1. 使用final关键字:可以将String对象声明为final,这样就无法对其进行修改。
  2. 使用private访问修饰符:将String对象的成员变量声明为private,并提供只读的访问方法,不提供修改方法。
  3. 不提供修改方法:不提供任何修改String对象的方法,只提供获取值的方法。

String的底层是通过字符数组实现,由final修饰符修饰类和数组,private修饰属性,不提供get set方法。

  1. 为什么有了string还要stringbuffer,buffer和string的区别,性能比较

String 线程安全,适合少量数据的场景,每次操作(拼接、替换等)都会创建一个新得独享;
StringBuffer 可变字符串,线程安全,内部方法同步锁实现,适用于多线程字符串缓冲区操作大量数据
StringBuilder 可变字符串,线程不安全,内部没有对方法实现同步锁,适用于单线程字符串缓冲区操作大量数据

  1. Thread Local的原理,为什么Thread Local会有内存泄露问题,如何解决的?

https://www.cnblogs.com/xbhog/p/15411167.html
谈谈你对ThreadLocal的理解
1.ThreadLocal可以实现【资源对象】的线程隔离,让每个线程各用各的【资源对象】
,避免争用引发的线程安全问题
2.ThreadLocal同时实现了线程内的资源共享
3.每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
a)调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线
程的ThreadLocalMap集合中
b)调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
c)调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
4.ThreadLocal内存泄漏问题
ThreadLocalMap中的key是弱引用,值为强引用;key会被GC释放内存,关联value
的内存并不会释放。建议主动remove释放key,value

  1. hashmap插入过程,concurrenthashmap的优化,1.7和1.8的区别

HashMap的插入过程如下:

  1. 首先,根据键的hashCode()方法计算出哈希值。
  2. 使用哈希值和HashMap的容量进行位运算,得到键值对应的数组索引。
  3. 如果该索引位置为空,直接将键值对插入到该位置。
  4. 如果该索引位置已经存在其他键值对,可能是因为发生了哈希碰撞(不同的键计算出了相同的哈希值)。此时,会使用equals()方法比较键是否相等。
    • 如果键相等,则更新该位置的值。
    • 如果键不相等,则将键值对插入到链表或红黑树(JDK 1.8之后)中。

ConcurrentHashMap是HashMap的线程安全版本,它在1.7和1.8中有不同的实现方式和优化:
在JDK 1.7中,ConcurrentHashMap使用了分段锁(Segment)的机制。它将整个哈希表分成多个段(Segment),每个段维护一个独立的哈希表,不同的段可以被不同的线程同时访问,从而提高并发性能。每个段内部使用了可重入锁来保证线程安全。
在JDK 1.8中,ConcurrentHashMap进行了重大改进。它引入了CAS(Compare and Swap)-乐观锁 操作和synchronized关键字来实现并发控制。它使用了一种称为"数组+链表+红黑树"的数据结构,当链表长度超过一定阈值时,会将链表转换为红黑树,以提高查找的效率。同时,使用CAS操作来保证线程安全,避免了使用锁带来的性能开销。
总的来说,JDK 1.8中的ConcurrentHashMap在并发性能上有了很大的提升,并且在处理哈希碰撞时使用了红黑树来提高查找效率。而JDK 1.7中的ConcurrentHashMap则采用了分段锁的方式来实现线程安全。

  1. 四种引用类型,以及各自的应用场景

在Java中,有四种引用类型,分别是强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。它们各自有不同的应用场景。

  1. 强引用(Strong Reference):
    强引用是最常见的引用类型,如果一个对象具有强引用,垃圾回收器就不会回收它。即使内存不足,垃圾回收器也不会回收被强引用引用的对象。例如:
Object obj = new Object();

在上述代码中,obj就是一个强引用,只要obj存在,对象就不会被回收。

  1. 软引用(Soft Reference):
    软引用用于实现内存敏感的缓存。当系统内存不足时,垃圾回收器可能会回收被软引用引用的对象。使用软引用可以避免内存溢出的问题。例如:
SoftReference<Object> softRef = new SoftReference<>(new Object());

在上述代码中,softRef就是一个软引用,当系统内存不足时,垃圾回收器可能会回收softRef引用的对象。

  1. 弱引用(Weak Reference):
    弱引用用于实现一些特定的功能,比如ThreadLocal和WeakHashMap。当垃圾回收器进行垃圾回收时,无论内存是否充足,都会回收被弱引用引用的对象。例如:
WeakReference<Object> weakRef = new WeakReference<>(new Object());

在上述代码中,weakRef就是一个弱引用,当垃圾回收器进行垃圾回收时,可能会回收weakRef引用的对象。

  1. 虚引用(Phantom Reference):
    虚引用是最弱的引用类型,它的主要作用是跟踪对象被垃圾回收的状态。虚引用必须和引用队列(ReferenceQueue)一起使用,当垃圾回收器准备回收一个对象时,如果该对象有虚引用,会将虚引用加入到引用队列中。例如:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

在上述代码中,phantomRef就是一个虚引用,当垃圾回收器准备回收phantomRef引用的对象时,会将phantomRef加入到queue引用队列中。
总结一下,不同的引用类型在Java中有不同的应用场景。强引用用于正常的对象引用,软引用用于实现内存敏感的缓存,弱引用用于特定功能的实现(如ThreadLocal和WeakHashMap),虚引用用于跟踪对象被垃圾回收的状态。根据具体的需求和场景,选择适合的引用类型可以提高程序的性能和内存利用率。

  1. mysql索引种类,聚簇索引和非聚簇索引的区别

索引种类:

  1. 主键索引
  2. 二级索引
    1. 唯一索引
    2. 普通索引
    3. 前缀索引
    4. 全文索引

聚簇索引:单表只存在一个聚簇索引,主键,数据与索引是在一起的,保存的是整行的数据,B+树中的叶子节点
非聚簇索引:单表存在一个或多个,数据与索引是分开的,保存的是索引的值,B+树中的非叶子节点

  1. mysql的四种隔离级别,解决哪些问题,mvcc的实现原理

MySQL提供了四种隔离级别,它们分别是:

  1. 读未提交(Read Uncommitted):在该隔离级别下,一个事务可以读取到另一个事务尚未提交的数据。这种隔离级别最低,可能导致脏读、不可重复读和幻读的问题。
  2. 读已提交(Read Committed):在该隔离级别下,一个事务只能读取到已经提交的数据。这种隔离级别可以避免脏读问题,但仍然可能出现不可重复读和幻读的问题。
  3. 可重复读(Repeatable Read):在该隔离级别下,一个事务在执行期间多次读取同一数据时,能够保证读取到的数据是一致的。这种隔离级别可以避免脏读和不可重复读问题,但仍然可能出现幻读的问题。
  4. 串行化(Serializable):在该隔离级别下,事务会按照顺序逐个执行,每个事务都会完全独立运行,相当于将并发执行的事务串行化。这种隔离级别可以避免脏读、不可重复读和幻读的问题,但会影响系统的并发性能。

可以通过设置MySQL的隔离级别来控制事务的隔离程度,可以使用以下语句设置隔离级别:

SET TRANSACTION ISOLATION LEVEL <隔离级别>;

其中,<隔离级别>可以是上述四种隔离级别之一。需要注意的是,不同的隔离级别会对系统的性能和并发性产生不同的影响,选择合适的隔离级别需要根据具体的业务需求进行权衡。
MVCC(Multi-Version Concurrency Control)是一种并发控制机制,用于在数据库系统中实现并发事务的隔离性。它通过为每个事务和数据行分配唯一的版本号来实现。
MVCC的实现原理如下:

  1. 版本号:每个数据行都会有一个版本号,用于标识该数据行的历史版本。通常情况下,版本号是一个递增的整数。
  2. 事务ID:每个事务也会有一个唯一的事务ID,用于标识该事务的开始时间。
  3. 读操作:当一个事务执行读操作时,它会获取该数据行对应的最新版本号,并将该版本号与自己的事务ID进行比较。如果该版本号大于等于事务的ID,说明该数据行是可见的,事务可以读取该数据行。如果该版本号小于事务的ID,说明该数据行是不可见的,事务无法读取该数据行。
  4. 写操作:当一个事务执行写操作时,它会为该数据行创建一个新版本,并将该版本号与自己的事务ID关联。同时,该事务会将新版本的数据写入到数据库中。
  5. 回滚:当一个事务被回滚时,它会释放所有由该事务创建的版本。这样,其他事务就可以通过版本号判断该数据行是否可见。

MVCC的优点是可以提高并发性能,因为读操作不会阻塞写操作,同时读操作也不会阻塞其他读操作。它可以避免脏读、不可重复读和幻读等并发问题。
需要注意的是,MVCC并不是MySQL中所有存储引擎都支持的特性,例如MyISAM引擎就不支持MVCC,而InnoDB引擎则支持。

  1. mysql死锁及出现的场景

https://juejin.cn/post/7069990366597873695#heading-8

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
对于mysql而言,表锁不会产生死锁。而行锁则会产生死锁(原因是:表锁一次性锁定所有的表)

由于加锁顺序不一致导致的死锁
商品表goods表有商品G1,G2,G3 ,… n中商品。 用户A的购物车添加的商品有G1,G2。用户B添加到购物车的商品有G2,G1,G4。当用户结算的时候,需要将goods表中对应的商品库存减1。此时,用户A与用户B同时结算。

用户A 用户B
select * from goods where good_id=G1 for update select * from goods where good_id=G2 for update
对商品G2加锁,处于等待… 对商品G1进行加锁,等待…产生死锁

在上表的情况中,由于加锁顺序不一致导致死锁产生。
解决办法: 对需要加锁的记录一次性加锁

用户A: select * from goods where good_id in (G1,G2) for update; #获取锁成功
用户B: select * from goods where good_id in (G2,G1,G3) for update; # 处于等待状态

用户A: 更新库存以及其他业务操作提交事务
用户B:获取到锁

用户A: 更新完毕
用户B:更新库存以及其他业务操作提交事务
  1. Redis的基本数据结构,哨兵如何实现通信,结点宕机如何处理

Redis是一种基于内存的键值存储系统,支持多种数据结构。以下是Redis的基本数据结构:

  1. String(字符串):最基本的数据结构,可以存储字符串、整数或浮点数。
  2. Hash(哈希):类似于关联数组,可以存储字段和值的映射关系。
  3. List(列表):按照插入顺序存储一组字符串。
  4. Set(集合):无序、不重复的字符串集合。
  5. Sorted Set(有序集合):类似于集合,每个成员都关联一个分数,用于排序。

哨兵(Sentinel)是Redis的高可用解决方案,用于监控和管理Redis的主从复制和故障转移。哨兵之间通过发布订阅(Pub/Sub)机制进行通信。当一个哨兵节点发现主节点宕机后,它会通过发布消息通知其他哨兵节点,其他哨兵节点会进行投票选举出新的主节点,并将这个信息广播给其他节点。一旦新的主节点选举完成,哨兵节点会通知客户端进行更新。
当一个Redis节点宕机时,哨兵会进行故障转移来确保系统的可用性。哨兵会选举出一个新的主节点,并将其他从节点切换到新的主节点。哨兵还会监控从节点,如果从节点宕机,哨兵会将其标记为下线,并尝试将其重新配置为新的从节点。
在结点宕机的情况下,哨兵的处理流程如下:

  1. 哨兵检测到主节点宕机。
  2. 哨兵节点进行选举,选举出新的主节点。
  3. 哨兵节点通知其他节点进行主从切换。
  4. 客户端收到通知后更新配置,连接到新的主节点。

通过这种方式,哨兵能够实现Redis的高可用性和故障恢复能力。

  1. Spring Boot的自动装配流程

在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个
注解是对三个注解进行了封装,分别是:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
其中 @EnableAutoConfiguration 是实现自动化配置的核心注解。
该注解通过 @Import 注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。
在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
一般条件判断会有像 @ConditionalOnClass 这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器 中使用。

  1. Spring Bean的安全问题

https://cloud.tencent.com/developer/article/1743283

bean在spring中的作用域有5种,常用的一般是单例和原型,原型的话是每次使用都会创建一个新的bean,所以不存在bean的线程安全问题,单例下的bean在spring容器中只有一个,如果bean是无状态的像dao\service中的bean,这种不存在线程安全问题,如果是有状态的,bean中有属性操作的,数据存储的,是有线程安全问题,解决的方式可以使用Treadlocal来实现资源的线程隔离。

有状态就是有数据存储功能 无状态就是不会保存数据

  1. Spring如何解决循环依赖问题

循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A 循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:缓存早期的bean对象(生命周期还没走完)
③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
具体流程:
第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories
第二,A在初始化的时候需要B对象,这个走B的创建的逻辑
第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories
第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象 同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键
第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects
第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects
第七,二级缓存中的临时对象A清除

  1. MyBatis的分页是内存分页还是物理分页,分页插件的实现原理

物理分页,也就是通过MySQL limit查询的。
通过mybatis提供的插件接口,实现自定义的插件,在插件的拦截方法内拦截执行的sql,然后重写sql,进行物理分页语句和分页参数的设置。

  1. MyBatis的sql执行原理(核心是代理)

读取mybatis的配置文件,通过配置文件创建sql会话工厂,会话工厂创建一个sqlSession,sqlSession包含了执行SQL的所有方法,通过数据库操作接口,Executor执行器,并维护响应的缓存信息,Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息,用于输入参数的映射,输出结果映射

你可能感兴趣的:(面经题解,java,mybatis,redis,数据库)