前言:笔者认为,面试中最重要的不是你回答出了多少问题,而是自信!在听完面试官的问题时,不要立刻回答,停顿三秒。一来给面试官留下你在思考的印象,二来有个整理思路的时间,让回答更有逻辑性。


文章目录

  • 一、Java基础面试题
  • 1、面向对象
  • 2、HashMap底层原理
  • 3、 ==和equals(常见于笔试)
  • 4、 ArrayList 和LinkedList区别
  • 二、Java并发面试题
  • 1、 ThreadLocal
  • 2、synchronized相关问题
  • 3、 多线程(线程池)
  • 4、并发相关问题
  • 三、mysql相关问题
  • 1、Mysql索引
  • 2、数据库分库分表
  • 3、数据库事务
  • 4、Mysql其他问题
  • 四、JVM虚拟机
  • 1、JVM常问面试题
  • 2、JVM进阶面试题
  • 五、Redis面试题
  • 1 、缓存雪崩、缓存穿透、缓存击穿
  • 2、 如何保证数据库和Redis的数据一致性
  • 3、Redis的数据结构及使用场景
  • 4、Redis持久化机制有哪些
  • 5、Redis其他常见问题
  • 六、SSM和SpringBoot
  • 1、Spring相关问题
  • 2、SpringMVC执行流程
  • 3、Springboot自动装配原理
  • 七、网络与IO面试题
  • 1、网络
  • 八、消息中间件
  • 1、Kafka
  • 2、RocketMQ
  • 3、Kafka与RocketMQ的对比
  • 九 、设计模式


一、Java基础面试题

1、面向对象

1.1 面向对象和面向过程的区别?
面向过程,是站在计算机的角度出发,以代码执行过程为核心,考虑到让计算机先做什么、再做什么。而面向对象,是站在人的角度,从现实生活出发。比如一个司机和车,是use a 的关系,不管司机开不开车,车这个对象都要有能跑的属性。说白了,人们生活中解决问题的思路其实就是面向对象。
1.2 面向对象的三大特性?

  • 封装:核心思想就是“隐藏细节”,对外提供访问属性的方法。保证了数据的安全和程序的稳定。
  • 继承:将子类共性的属性或者方法抽取出来成为父类,子类继承后就不用重复定义父类的属性和方法,只需要扩展自己的个性化。强制继承父类非私有的属性和方法,破坏了封装性原则。
  • 多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。
    继承,方法重写,父类指向子类对象。无法调用子类特有的功能;

父类类型 变量名=new 子类对象
变量名.方法名()

笔者在看设计模式和spring源码时,对面向对象有了进一步的认识。如果说封装和继承是面向对象的基石,那么多态则是面向对象的具体使用。编译看左边,运行看右边。 在日常书写代码时,尽量使用List list=new ArrayList();这种形式,左边为抽象父类,右边为具体的实现子类。这样的书写方式,不仅代码兼容性更好而且具体的实现细节由具体的对象去执行完成,这才是面向对象的初衷和设计核心!

2、HashMap底层原理

2.1 什么是哈希表?哈希冲突?如何处理哈希冲突?
1)、什么是哈希表?
如何在一个无序的线性表中查找一个数据元素?答:对着线性表遍历!但这样时间复杂度会很高,查找耗费性能。那有没有这样一种数据结构,数据结构中的元素和它所在位置有一个对应关系,这样就可以通过元素找到它的位置,时间复杂度就降低了。这种数据结构就是哈希表!
哈希表又叫散列表,通过映射函数f(key)将一组数据散列存储在数组中的一种数据结构。在哈希表中,每个key和它所在位置都有一个映射关系,可以通过f(key)来轻易找到。比如映射关系f(key)=key/11;
2)、什么是哈希冲突(哈希碰撞)?
比如f(key)=key/11,当值为key为6和72时,余数都是6,72该怎么存呢?这就是哈希冲突!
3)、如何减少哈希冲突?
哈希冲突不可避免,但能减少!这里提供几种最容易理解的几种解决方案:
1)再哈希法:选取若干个不同的哈希函数,当发生哈希冲突时,执行另外的哈希函数,直至不冲突为止。
2)链地址法(最常用!!!):这个就是与红黑树相关了!当我们出现[48,15,26,4,70,82,59,28,6,17]。我们这组数据仍然散列存储到长度为11的数组中,出现多个能被11整除的元素时(6,28,17),就会在第一个余数为6的元素下依次挂上,即形成所谓的链表!但链表太长查询效率又会降低,为了优化时间复杂度,当链表长度大于8时,会形成红黑树(可自平衡的二叉红黑树)
4)、哈希表的扩容与Rehash
在链表长度不变的情况下,插入元素越多,越容易哈希冲突。这时就进行扩容,当装载因子填充到一定数值(一般选0.75),就进行2倍扩容,然后将原来的数据重新装到新的扩容好的数组中(该过程即Rehash)

2.2 hashmap为什么是线程不安全的?
在并发下put数据,会发生数据覆盖、数据错乱。hashtable用的是synchronized关键字,而ConcurrentHashMap在jdk1.7之前是用了粒度更小的分段锁,1.8之后用的是synchronized+cas锁。
cas锁:没有获取到锁的线程不会阻塞,而是会循环控制不断获取锁。
2.3 HashMap扩容机制
jdk1.7,是数组+链表,扩容时会新生成一个数组,遍历老数组的每个链表上的元素,取每个元素的key,基于新数组的长度,计算出在新数组中的位置,将元素添加进去;
jdk1.8,是数组+链表+红黑树,扩容与jdk1.7时大致相同,不同的是,一棵红黑树进行计算时可能在新的数组中形成2个链表或是1个链表+1个红黑树。
2.4 说一下HashMap的Put()方法?
在jdk1.7和jdk1.8,都是先根据key哈希计算出在数组中的位置。

  • 1、如果数组下标为空,则将key和value封装成一个Entry对象(jdk1.7是Entry对象,jdk1.8是Node对象),直接添加。
  • 2、数组下标不为空,jdk1.7先判断是否需要扩容,不需要则通过头插法插入到链表中;jdk1.8,先判断当前Node节点上是红黑树还是链表,先生成一个Node节点通过尾插法进行插入,然后在进行扩容判断,不需要扩容则结束Put()。

2.5 CorrentHashMap()如何保证线程安全?

  • 并发工具类-ConcurrentHashMap1.7原理
  • 北京java内推怎么找 北京java面试_java

  • 并发工具类-ConcurrentHashMap1.8原理
  • 北京java内推怎么找 北京java面试_java_02

jdk1.8时的总结 :

1,如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表
2,计算当前元素应存入的索引。
3,如果该索引位置为null,则利用cas算法,将本结点添加到数组中。
4,如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。
5,当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性

3、 ==和equals(常见于笔试)

== : 对于基本数据类型的变量,比较的是值。引用类型比较的地址值。

equals:equals方法不能作用于基本数据类型。对于引用类型的变量,没有重写equals比较的是内存地址;重写equals比较的是变量所指的对象内容。

4、 ArrayList 和LinkedList区别
  • ArrayList:
    基于动态数组,内存空间连续,所以查询快。但如果使用尾插法,并且制定好初始容量,会使性能极大提高,甚至超过LinkedList的插入速度。
  • LinkedList:
    基于链表,可以存储在分散的内存中,适合做增删,不适合查询。

两者都实现了List接口,但LinkedList额外实现了Deque接口,所以LinkedList还可以当队列来使用。

二、Java并发面试题

1、 ThreadLocal

1.1 谈谈你对ThreadLocal的理解?
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的。它不是针对程序的全局变量,只是针对当前线程的全局变量。

1.2 ThreadLocal底层实现原理?
Threadlocal内部有一个非常关键的内部类ThreadlocalMap,里面定义了一个由key - value组成的Entry数组,key存放的就是当前的Threadlocal对象,value为我们所需的数据。在使用完之后最后进行一个清除,因为我们的SpringMvc使用的是线程池,为了避免前世的影响!!! key是弱引用会被gc清除,value是强引用 不会被清除,所以会造成内存泄漏。 解决的话就是用 remove方法。

public void set(T value) {
 //进行set方法时,先获取当前线程对象,根据当前线程获取ThreadLocalMap,
 //然后以当前Threadlocal为key进行存储
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
2、synchronized相关问题

2.1 synchronized与Reentrantlock的区别

  • synchronized是一个关键字,属于JVM层面的,Reentrantlock是一个类,是API层面的;
  • synchronized是自动加锁、释放锁,Reentrantlock则需要手动加锁和释放;
  • synchronized底层有一个锁升级的过程(偏向锁—轻量级锁----重量级锁)

当一个线程获取一个琐时,此时该锁为偏向锁,当第二个线程尝试获取锁时,该锁会升级为轻量级锁,底层通过自旋来实现(不会造成线程阻塞),当自旋次数过多,会升级为重量级锁,会造成线程阻塞。

2.2 volatile关键字如何保证可见性和有序性

  • 可见性:对加了volatile的成员变量进行修改时,会直接将cpu高级缓存区的数据放到主内存中,对这个数据的读取,会直接从主内存中取。
  • 有序性:对加了volatile的成员变量进行读写时,会插入内存屏障,防止指令重排,保障了有序性。

volatile只有写操作是原子性的,也就是数据操作完成后会立刻刷新到主内存中。但是被volatile修饰的变量在读的时候可能会被多个线程读,所以它不能保证原子性。

2.3 Java如何避免死锁

  • 注意加锁的顺序,保证每个线程按顺序进行加锁;
  • 加锁时限,可以设置一个超时时间;
  • 注意死锁检查,这是一种预防机制,可以确保发生死锁的第一时间进行处理。
3、 多线程(线程池)

3.1 线程有哪些状态(生命周期)

新建、就绪、运行、阻塞、死亡

北京java内推怎么找 北京java面试_数据库_03


3.2 如何获取多线程的返回值?

深坑!如果问多线程的创建方式,你一定知道是继承Thread类,实现runnable,callable接口。这里就是拐了个弯,变相的了解有返回值的callable接口。通过中间媒介FutureTask,将实现callable接口的类对象传递进去,调用FutureTask里面的get()方法,即可获取多线程的返回值。

3.3 为什么使用线程池,几个参数?
降低资源消耗(创建、销毁耗资源),提高响应速度(任务来了,可以有线程直接使用);

  • 核心线程数、 最大线程数量、最大空闲时间、 空闲时间单位、任务队列、 线程工厂、拒绝策略;
4、并发相关问题

4.1、 并行、串行、并发?

  • 并行: 同一时刻,多个任务互不干扰的同时进行;
  • 串行:任务排队,一个一个执行;
  • 并发:同一时刻,只有一个任务,多个任务交替执行;

4.2、 谈谈对AQS的理解,AQS如何实现可重入锁?

  • AQS是一个Java线程同步的框架,是JDK很多锁的核心实现框架。
  • 在AQS中,维护了一个信号量state和一个由线程组成的双向链表队列。其中,这个线程队列就是给线程排队的,而state就像是红绿灯,用来控制线程排队或者放行的。不同场景下,有不同的意义。
  • 在可重入锁(锁了还可以再锁)场景下,state表示加锁次数,0表示无锁,每加一次锁,state就加1,释放锁state就减1。点击阅读 并发编程小结

三、mysql相关问题

笔者总结了一篇Mysql高级,涉及内容较深些,也是常问的面试题,点击链接查阅

Mysql高级篇

1、Mysql索引

1.1 索引的类型可以是String类型吗?
聚簇索引----数据和索引放一块,像主键索引,具有唯一性(Innodb就是)
数据库第一范式:必须要有id,这个id是自带索引的。

一般用自增id,字符串可以做id,但是不好,像uuid做的id是随机的,都没有排序!!!不像自增id维护索引的成本会很低

北京java内推怎么找 北京java面试_java_04

1.2 什么是索引?什么情况下用索引?什么时候不用?

1)就是一种数据结构,目的就是为了快速查找数据。
2)对查询频率高(索引就是为了提高查询效率),像where后的字段
数据量特别大, 索引不是越多越好,会影响增删的效率,典型的用空间换时间。
分组字段可以建立索引,因为分组的前提是排序(覆盖索引)
3)频繁更新的字段、查询少的、参与计算的不适合建索引。

1.3 索引失效?
当出现了% > < =,会进行全表扫描,以及当mysql优化器分析数据量较多,走全表比走索引更快。

1.4 查看索引使用和查看索引信息?
索引使用: explain 结果 (只要数字大于1)1 row in set 即生效了
查看索引信息: show indexs from 表名

1.5 复合索引?最左匹配原则?
最核心的是 等值比较
复合索引就是多个字段放一块(企业最常用的是符合索引!!!唯一索引,普通索引我们也用)
mysql会一直向右查询,直到遇到范围查询(> < like),比如用 a b c d四个字段创建了一个复合索引, a=3 b=5 c>7 d=9 只会用到前三个,因为b c d 是根据a 的后面进行规则的排序,即a是有序的,后面的bcd是无序的。 (hash索引用的不多,因为无序,但是定位快,了解即可)

1.6 Btree和B+tree?为什么mysql用的是B+ tree?**
B+tree是Btree的升级版。
Btree:多路平衡树,当增删数据时,会自动的将数据进行这个平衡(旋转)
为什么从Btree转到B+tree? 因为:索引也是一个文件,存档在磁盘,在使用时读入到内存。内存是有限的,如果索引文件过大,无法一次性全部加载,需要分批加载。在有限磁盘的限制下,B+tree可以减少磁盘的I/O。

对于B+tree,所有的数据都存在叶子节点,根和非叶子节点只是存储的指针,指向下一个数据的地址,由叶子节点再去查找到关联的数据信息。对于查询的数据,都要从root节点走到叶子节点,所以查询相比于Btree更加稳定。

北京java内推怎么找 北京java面试_面试_05


对于mysql,是在B+tree的基础上,在相邻节点间增加了一个链表指针,形成了带有顺序的B+tree,提高了区间的访问性能。

北京java内推怎么找 北京java面试_java_06


1.7 覆盖索引与回表

如果只需要在一棵索引树上就可以获取Sql所需要的所有列,就不需要回表查询,这样查询速度会更快。而实现覆盖索引最快的方式就是将所需要的字段放在一起建一个联合索引。

1.8 Mysql的锁有哪些?什么是间隙锁?
从锁的粒度来分
1、行锁:加锁粒度小,但是加锁的资源开销比较大。Innodb支持。
1)共享锁:多个事务可以共享一把锁,但是只能读不能修改
2)排他锁:只有一个事务可以获得排他锁,其他事务不能获取该行的锁。Innodb会自行对增删改操作添加排他锁。
2、表锁:加锁粒度大,加锁的资源消耗小,Mysalm和Innodb都支持。
3、全局锁:加锁后全库都处于只读状态,用于全库数据备份。

2、数据库分库分表

2.1 数据库的分库分表?什么时候分?怎么分?
当单表数据超过1000W时,很多操作的性能会下降,所以需要切分,以减少数据库的压力,缩短查询时间。
垂直切分:将关系联系不紧密的表进行分库,将一张表中不常用的字段进行抽取新建一张表。优点类似于微服务。
水平切分:当一个应用难以再细粒度的垂直切分,根据数据间的逻辑进行划分,比如客户、存款、支付;
2.2 数据库的优化?
1)sql优化以及索引的优化,索引建立要合理,过多会影响增删性能
2)数据可设计要满足他的三大范式、五大约束
3)硬件优化

3、数据库事务

spring里面的事务和mysql里面的事务是一个概念,如果mysql不支持事务,加上@transation也是无效的。但是spring里面的@transation不能用于分布式环境下,分布式多线程下用的是senta+@globalTransation注解。
3.1 @transation用于类和方法有什么区别?
@transation只能修饰public方法。
类上:相当于在所有的public方法上加上了@transation注解
方法:会覆盖类上的配置。

3.2 事务的4个条件ACID
原子性:所有的操作是一个整体,要么全部成功,要么全部失败(回滚)
一致性:事务开始前后,数据库的完整性没有被破坏,也就是写入的资料完全符合我们的预设。
隔离性:允许多个并发事务对数据进行读写操作,它可以有效的防止多个事务并发执行时由于交叉执行而导致数据的不一致。(隔离性里面有4个隔离级别:读未提交、读以提交、可重复读、串行化)
持久性:事务完成,对数据的操作就是永久的,即使系统故障也不会丢失。

4、Mysql其他问题

4.1 mysql中的null和空值有什么不一样?
1)空值是不占空间的,null值占用空间。两者就像:空值是真空状态的杯子,而null值是装满空气的杯子。
2)查寻上:null 值是用is null/is not null来查询,而空值( ’ ’ )则可以用 = > !=等。 在使用聚合函数count时,会过滤掉null,而不会过滤掉 ( ’ ’ )值。
在实际开发中,没有特定需求,可以直接使用空值!!!

四、JVM虚拟机

笔者此前在其他博客上整理过,不在重复,链接如下:

1、JVM常问面试题

JVM常问面试题

2、JVM进阶面试题

JVM进阶面试题

五、Redis面试题

1 、缓存雪崩、缓存穿透、缓存击穿
  • 缓存雪崩: 缓存同一时间大面积失效,大量请求打到数据库上,数据库承受不住崩掉;
    解决方案:过期时间设置成随机、缓存预热(系统出初启动,先存数据到缓存里)
  • 缓存穿透: 数据库和redis中都没有数据,导致大量数据查询数据库,导致数据库崩掉;
  • 可以将不存在的请求key的value 设置成一个字符串返回,这样就避免了黑君攻击到数据库。
    解决方案: 接口层进行校验(id<0的直接拒绝)、采用布隆过滤器
  • 缓存击穿: redis的一个key失效,请求全部打到数据库(缓存没有数据库有);
    解决方案: 热点数据永不过期
2、 如何保证数据库和Redis的数据一致性

当我们遇到这个问题时,要考虑是先删缓存,还是先写数据库。
延时双删: 先删redis缓存数据,再更新数据库,然后再删redis缓存,这样就避免了删除redis和更新数据库这期间,其他的线程读不到redis里的值,去读数据库里的旧数据。

将操作可串行化(先写在读就可以了)
1)先删缓存,将更新数据库操作放到一个有序队列中
2)从缓存中查不到的查询操作,都进入有序队列(MQ)
问题:大量读请求积压--------> 将队列水平拆分

3、Redis的数据结构及使用场景

String :字符串 ----------原子计数器
List :列表 ---------微博热搜Top10
Set :无序集合 ---------好友推荐
Zset :有序集合 -----------排行榜
Hash :哈希表 ----------适合存储对象

----------------------------------前五种为常见,后四种为面试加分项---------------------------------------------
bitmap:布隆过滤器
GeoHash:坐标,(存储坐标的)基于Zset的score进行排序就可以得到坐标附近的值
HyperLoglog: 统计不重复数据,用于大数据基础使用
Streams:内存版的Kafka,消息的订阅发布

4、Redis持久化机制有哪些

基于内存,宕机数据会消失,所以需要持久化。有两种方式

  • RDB:Redis DateBase
    在指定的时间间隔内将内存中的数据以快照的形式写入磁盘,实际是fork(分支)一个子进程,先将数据复制写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

优点:

  • 整个redis数据库将只有一个dump.rdb文件,方便持久化;
  • 容灾性好,方便备份;
  • 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不进行任何的IO操作,保证了Redis的高性能;
  • 相对于数据集大时,比AOF的启动效率更高;

缺点:

  • 数据安全性低。持久化期间Redis发生故障,会有数据丢失;

AOF: Append Only File
以日志的形式记录服务器的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录

优点:

  • 数据安全,每次修改都会被记录到磁盘中;
  • 通过append模式写文件,即使Redis宕机,也不会破坏已经存在的内容,可以通过redis-check-aof工具解决数据一致性问题;

缺点:

  • 效率没有RDB高;
  • AOF文件比RDB大,且恢复速度慢;
5、Redis其他常见问题

5.1 Redis线程模型,单线程为什么快?

单线程快的原因有以下几点:

  • 纯内存操作
  • 核心是基于非阻塞的IO多路复用机制(单个线程可以同时处理多个请求)
  • 单线程避免了多线程来回切换的上下文环境切换问题

5.2 Redis分布式锁的实现原理
set(key,value,setnx,setpx)
setnx----------->加锁的命令
setpx----------->锁的过期时间
setnx+setp在高版本中已经整合为一个原子命令。
当任务超时,锁自动释放怎么办?可以使用redission的看门狗,会自动续期。

5.3 Redis主从复制的原理

简述好下面的流程图即可

北京java内推怎么找 北京java面试_数据_07

5.4、Redis 到底是单线程还是多线程?
Redis6.0 版本之前的单线程指的是网络 I/O 和键值对读写是由一个命令完成的;
Reds6.0 版本之前只有网络请求模块和数据操作模块是单线程的,而其他的特久化,集群数
据同步,其实是由额外的线程完成的
Redis6.0 版本之后的多线程指的是网络请求过程采用了多线程,而键值对的读写命令依然是单线程处理,所以Redis 是线程安全的。命令的执行排队执行
5.5、 单线程为什么快?
内存操作、单线程操作没有线程切换开销、全局 hash表 〈一维数细:链表〉查找快
5.6、Redis 底层数据如何用跳表来存储的?
跳表:将有序链表改造为支持近似“折半查找”算法,可以快速的插入、删除、查找操作。
5.7、Redis 的Key过期了,为什么没有释放?
有两种删除策略:
惰性删除:当读到一个过期的key时触发删除
定时删除:惰性删除无法及时删除,所以 Redis默认每 100ms 主动删除一批己过期的 key,导致可能出现部分 key 过期但没被清理的情况,导致内存并没有被释放。
小坑:如果使用set命令修改某个key 的valve 值,没有加过期时间参数,这个key会被设定为永久有效。
5.8、 Redis 没设置过期期间,为什么被 Redis 主动删除了?
当Redis 已用内存超过最大内存时,会触发王动清理策略(8种),对过期key所有 key设置了处理。可能触发了对所有key 的筛选删除(LRU:最近最少使用,LFU:最少频率使用)。
5.9、删除key 的命令,会阻塞 Redis吗?
删除 String 类型,时间复杂度 o(1),删除单个列表、集合或 hash 类型的key时间复杂度为 o(M);
5.10、一次线上事故,Redis 主从切换导致了缓存雪崩
(要保证主从库机器时钟一致)我们假设,slave 的时钟比master 的时钟快很多。我们在master 里设置了过期的key, slave的角度看,可能会有很多在master里没有过期的数据己经过期了。如果此时操作主从切换,把 slave提升为新的master,就会开始清理大量的key;会导致以下结果:
① Master大量清理 key,主线程阻塞,无法及时处理客户端请求;
② Redis 大量数据过期,引发缓存雪崩。

5.11、线上Redis 持久化策略一般如何设置?
默认 RDB, 如果对性能要求较高,在master最好不要做持久化,可以在某个 slave 开启 AOF
备份数据,策略设置为每秒同步一次即可。
5.12、一次线上事故,Redis 主节点宕机导致数据全部丢失
如果 Redis 采用以下模式部署,就会发生数据丢失问题:
master-slave+哨兵部署实例 +master 没有开启持久化功能 +Redis 进程使用supervisor管理,并配罟宕机重启。
这样在master言机,哨兵还未发起切换,supervisor 会将 master 立马拉起,但因为 master
没有设置持久化,就相当于是空实例,其他的 slave为了会master 保持数据同生,也会变成一个个空实例,也就发生了数据丟失。
避免:不要给 Redis 主节点设置宕机立马重启,而应该等哨兵把某个 slave选为 master之后,在重启原来宕机的主节点,让其变成 slave。
5.13、Redis主从复制风暴是怎么回事?
Redis 主节点有多个从节点,在某一时刻主节点挂了,重启后从节点重新连上主节点后,进行全量复制,数据量大,加重网络和服务器的页载。避免方法:使用哨兵或集群模式。

六、SSM和SpringBoot

1、Spring相关问题

Spring必问ioc和aop,基本程序员都知道,但如果想要获得好的印象,拉开差距,就要回答出更深的理解,阅读过源码是最好的。

1.1 Spring是什么?谈谈IOC和AOP的理解
Spring是一个企业级的应用框架,减化了配置,简化了项目部署环境。

IOC: 容器概念、控制反转、依赖注入

  • ioc容器:
    实质就是一个map(key,value)里面存放了各种对象(xml里的bean节点,@Service 、@Conroller、@Compent等),在项目启动时,会读取配置文件里的bean节点,根据全限定类名使用反射创建对象,存入到map中。这个时候map里就有我们需要的各种对象了,当我们需要使用时,通过DI(@Autowired、@Resource等注解,xml里的ref标签)注入的方式去取。
  • 控制反转:
    没有引入ioc容器前,我们A对象依赖于B对象,需要自己手动的去new,引入ioc后,需要B对象的时候ioc自己主动去创建一个B对象到A需要的地方。A对象获取B对象的过程,从主动变成了被动,这个就是控制反转的由来。
  • 依赖注入:
    依赖注入是实现IOC的方法,就是在IOC运行期间,主动的将某种依赖关系注入到对象中;

AOP:将程序中的交叉业务逻辑(日志、异常、安全)封装成一个切面,对符合其要求的对象进行一个增强,比如一个方法的增强,在方法执行前或执行后做一些额外的事(通过beanPostProcessor实现)。

1.2 说一下Spring的事务机制

Spring的事务机制是基于数据库事务和AOP。
对于加了@Transation的注解,会创建一个代理对象作为bean,当调用代理对象方法时,会判断该方法上是否加了@Transation的注解,如果加了,就利用事务管理器创建一个数据库连接,将autocommit改为false,禁止自动提交,事务结束没有异常就提交,反之回滚。

什么时候@Transation会失效?方法加了private私有时会失效。

1.3 Spring 的bean生命周期
Spring的bean生命周期里面涉及的内容很多,这里只是说一个大致流程,面试非大厂足够了

有一个类 UserService.class--------> 推断构造方法(默认无参构造)---------> 实例化(普通对象)----------> 依赖注入(属性赋值,加了@Autowire -------> 初始化前(@PostConstruct) --------> 初始化(initializingBean)--------> 初始化后(AOP)---------> 代理对象 ------>bean

读取配置信息获取到beandefinition(BeanDefinition 是定义 Bean 的配置元信息接口),存入到map中,推断bean的构造方法,进行实例化,然后进行属性赋值,在初始化后会进行aop的切面,如果创建的bean是单例的,会将单例bean放入到单例池中。之后进行bean的使用,使用完后会调用
destroy()方法进行销毁;

2、SpringMVC执行流程

1、具体执行流程如下,面试前背下来即可(截图可放大)!

北京java内推怎么找 北京java面试_数据_08


2、父子容器controller层相当于是spring的子容器,service层是spring的父容器,在controller层可以注入调用service层的bean对象,而service层则不可以调用controller的bean对象。这也是我们最初学习SSM框架时,在spring.xml排除扫描controller的原因。因为同样的一个类,如果spring和springmvc都扫描了,那么它会创建2个bean对象!

北京java内推怎么找 北京java面试_北京java内推怎么找_09

3、Springboot自动装配原理

Springboot有一个引导类叫SpringBootApplation,在这个类里面有很多注解,@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
与我们自动装配有关的是@EnableAutoConfiguration这个注解,这个注解里面有个@import注解,里面导入了AutoConfigurationImportSelector这样一个类,在这个类里面有个getCandidateConfigurations方法,这个方法就会找到Meta文件下的spring.factories,在spring.factories里面有很多全路径类名,通过反射加载到ioc容器中

问:spring.factories里面的类都会全部加载吗?不是的,符合条件的才会进行加载!

七、网络与IO面试题

1、网络

1.1 TCP和UDP的区别

  • TCP:面向连接、可靠的、传输层通讯协议。
    特点:好比打电话,点对点的,高可靠的,效率较低,占用的系统资源较多;
  • UDP: 面向无连接的、不可靠的、传输层通讯协议
    特点:好比广播,不需要连接,不管接收方有没有准备好,都可以直接发消息。效率高,有可能消息丢失,协议比较简单,所以占用的系统资源较少。

1.2 TCP为什么三次握手,四次挥手?

1)在建立TCP连接时,需要通过三次握手来建立,过程是:

客户端 ---------- 发送syn ----------------> 服务端

客户端 <-------- 发送syn-ack确认------- 服务端

客户端 ----------发送ack -----------------> 服务端

2)在断开TCP连接时,需要通过四次挥手来建立,过程是:

客户端 ---------- 发送 FIN ------------------------> 服务端

客户端 <-------- 发送ACK确认-------------------- 服务端 (表示我收到断开的消息,但还有数据要处理)

客户端 <----------发送FIN -------------------------- 服务端(表示数据处理完了,客户端可以断开了)

客户端 --------发送ACK(表示也会断开--------> 服务端

1.3 Http和Https的区别?

  • HTTP:是网络上应用最为广泛的一种网络通讯协议,是基于TCP协议的,可以使得
    浏览器工作更为高效,较少网络传输;
  • HTTPS:HTTP的加强版,可以认为是HTTP+SSL(Secure Socket Layer) 。在HTTP的基础上增加了一系列的安全措施。一方面保证了数据的传输,一方面对访问者增加了安全校验机制,是目前现行架构下,最安全的解决方案。 缺点是握手协议费时,会影响吞吐量以及服务器的响应速度。

主要区别:

  • http是免费的,https是需要申请证书,收费的
  • http默认端口80,https默认端口443

八、消息中间件

MQ如何保证消息的幂等性?
其实就是要消费者解决重复消费的问题,所有的MQ产品都没有提供解决消息幂等性的机制,需要我们在消费端做处理。
RocketMQ: 给每个消息分配了一个MessageID,这个MessageID就可以作为消费者判断幂等的依据(消息量巨大时,无法保证MessageID是唯一的),所以最好是自己带一个全局唯一的业务id。

如何保证消息不丢失?
1、哪些环节会造成消息丢失?

  • 主节点向从节点同步消息(跨网络),主节点挂了;
  • MQ是基于内存工作的,最终消息都是要存到硬盘。(断电内存消息丢失);

2、如何防止消息丢失?

  • 生产者发送消息不丢失
    Kafka: 消息发送+回调(ack机制)
    RocketMQ: ①消息发送+回调 ②事务消息
  • 北京java内推怎么找 北京java面试_数据库_10

  • MQ同步消息不丢失

RocketMQ:普通集群中,同步同步(消息来,主从同步好发送成功)、异步同步(消息来,主节点直接发送成功,再异步给从节点)。同步同步安全性高,同步异步效率高,有消息丢失可能。

Kafka:通常使用在允许消息少量丢失的场景

  • MQ消息存盘不丢失
    RocketMQ:(一个简单配置)同步刷盘(安全性高,消息不容易丢失)、异步刷盘(效率高、有消息丢失可能)
  • 消费者消费消息不丢失
    RocketMQ:使用默认的消费,不要使用异步消费。
    Kafka:手动提交offset
1、Kafka

1、Kafka高性能、高吞吐的原因

  • 磁盘的 顺序读写
  • 零拷贝:磁盘文件–>内存缓冲区
  • 批量压缩:多条消息一起压缩,降低带宽
  • 批量读写

2、Kafka是pull还是push?
pull,这样consumer端可以根据自己的消费能力来拉取,可以批量也可以单条拉取。
缺点:没有消息会导致consumer的空循环,浪费资源。解决方案:设置参数,让空循环阻塞;

2、RocketMQ
3、Kafka与RocketMQ的对比

1、Kafka为什么比RocketMQ吞吐量高
Kafka生产者采用异步发送消息,并不是直接向Broker发送,而是放入缓存,直接向业务代码发送成功消息。当缓存到达一定数量时,再向Broker发送消息,减少了网络IO,但降低了可靠性。

1)Kafka具备选举功能,当某个partition(分区)的master挂了后,该分区的某个slaver就会升级为master对外提供服务。而RocketMQ不具备选举功能,当某个master挂了,你可以写到其他的master,而不是升级某个slaver。
2)Kafka不支持消息的定时发送,RocketMQ支持。
3)Kafka(和Zookeeper强绑定)专注于大数据处理,是分布式流式系统。而RocketMQ更多的专注于业务,是一个消息中间件。

九 、设计模式

笔者总结设计模式时发现,很多设计模式底层运用的思想都是多态,不同多态的表现决定了不同设计模式的差别。

1、单例模式

  • 懒汉式(静态内部类方式):在真正使用时,才实例化
    1)线程安全问题;
    2)double check加锁优化 (使用synchronized加锁);
    3)编译期(JIT),CUP可能对指令重排,使用volatile关键字;

静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,所以只会被实例化一次,并且严格保证实例化顺序。

  • 饿汉式:利用的是jvm的类加载机制(类加载器、双亲委派),在类加载初始阶段就完成实例的初始化(如给类的静态变量赋初始值)。

2、抽象工厂模式(开闭原则:扩展开放、修改关闭)
程序需要处理不同产品,但又不想依赖具体的类,可以使用抽象工厂模式,向jdk里的Connection的接口利用的就是,我们平时写的接口与实现类分离,也是这个思想;避免了职责单一;

3、原型模式(spring里的prototype)

原型实例指定创建对象的种类,通过拷贝这些原型获取新的对象。
需要注意的是:按照原对象的属性拷贝,但拷贝出的是一个新的对象,引用地址是不同的。像ArrayList里面就有clone()(浅拷贝), BeanUtils.copyProperties(),也是用的原型模式。

4、享元模式
运用共享技术有效支持大量的细粒度对象
比如:字面量,String=“abc”;Interge

5、门户(Focade)模式

有一个统一的入口,根据不同的入参访问不同的子系统,不需要关心子系统是否变更,也是工作中最为常用的一种设计模式(比如聚合接口),这样能简化客户端的调用。

  • 应用场景:需要使用复杂有限的子系统接口直接调用;想将子系统简组织成层;

6、适配器模式
SpringMVC中使用该模式,DispatcherServlet会通过 handler 的类型找到对应适配器,执行目标 Controller 中的请求处理方法。

7、装饰者模式
不改变原有对象基础上,将功能附加到对象上。

8、模板(Template)设计模式
定义一个操作的算法骨架,而将一些步骤延迟到子类中去实现。比如jdbc的数据库链接、创建,具体的sql我们自己去写。还有RedisTemplate、MongoDBTemplate等都是运用的该模板,减少了大量重复的代码;

子类反向到父类,是模板设计模式的核心。在InputStream类中,read()方法最终是由具体的抽象子类实现,然后再到父类中进行后面的步骤,部分代码如下:

public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();   //最终调用具体子类的read()读取c个字节,在接着进行后面的步骤
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

9、观察者模式
多个对象监听一个主题对象,当主题对象变化时,所有依赖对象会收到消息并更新。Redis的哨兵模式(主从节点)、Zookeeper集群;

10、责任链模式
我们的过滤器就是使用的该模式
http的请求,比如: 一个request---->登录---->----->访问权限----->敏感词过滤----->…