本篇只是记录本人在工作或面试过程中遇到的一些问题及总结,如有错误,欢迎指正!
Java基础
1.Java普通类和抽象类的区别
普通类:
- 可以直接实例化为对象。
- 可以包含成员变量、成员方法和构造方法。
- 可以被其他类继承。
- 可以被其他类实现(如果实现了接口)。
- 可以重写父类的方法。
抽象类:
- 不能直接实例化为对象,只能作为基类供其他类继承。
- 可以包含成员变量、成员方法和构造方法。
- 可以包含抽象方法,抽象方法没有具体的实现,需要在子类中被实现。
- 子类必须实现抽象类中的所有抽象方法。
- 可以被其他类继承。
- 可以实现接口。
集合
1.List、Set、Map区别
List集合:有序、可重复
Set集合:无序、不重复
Map集合:无序,键值对形式。键不可重复,值可重复
2.List
2.1 ArryList
动态数组。延迟加载(懒加载),默认大小为10,每次扩容1.5倍,线程不安全,查询速度相对较快。
为什么说是懒加载?
因为当new一个ArryList集合时,并没有分配具体的容量,只有当第一次添加进集合里面时,才会分配容量。
2.2 LinkedList
使用双向链表方式存储数据。插入删除速度较快,线程不安全。
3.Map
3.1 HashMap
JDK1.8后,数组+链表+红黑树。当链表长度大于8时,链表会转换为红黑树(前提条件:当前数组长度大于了64),反之小于6时会还原成链表结构。
扩容条件:默认容量16,负载因子0.75(时间和空间复杂度最佳),所以第一次扩容阈值为12,每次扩容的容量为原来的2倍。
3.2 HashMap和HashTable
HashMap:键值可以为空,线程不安全。
HashTable:键值不可以为空,线程安全(过时弃用)
多线程
1. 线程和进程的区别?
- 进程是正在运行程序的实例,进程中包含线程,每个线程执行不同的任务
- 不同的进程使用不同的内存空间,在当前进程下的所以线程可以共享内存空间
- 线程更轻量,线程上下文的切换成本一般比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
2. 并行和并发的区别?
现在都是多核CPU,在多核CPU下
- 并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU
- 并行是同一时间动手处理多件事情的能力,例如:4核CPU同时执行4个线程
3. 创建线程的方式有那些?
共有四种:继承Thread类、实现Runable接口、实现Callable接口、线程池创建线程
3.1 继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread...run..");
}
public static void main(String[] args) {
// 创建MyThread对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 调用start方法启动线程
t1.start();
t2.start();
}
}
3.2 实现Runable接口
public class MyRunable implements Runnable {
@Override
public void run() {
System.out.println("MyRunable...run...");
}
public static void main(String[] args) {
// 创建MyRunable对象
MyRunable mr = new MyRunable();
// 创建Thread对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
// 调用start方法启动线程
t1.start();
t2.start();
}
}
3.3 实现Callable接口
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "OK";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建MyCallable对象
MyCallable mc = new MyCallable();
// 创建FutureTast
FutureTask<String> ft = new FutureTask<>(mc);
// 创建Thread对象
Thread t1 = new Thread(ft);
Thread t2 = new Thread(ft);
// 调用start方法启动线程
t1.start();
// 调用ft的get方法获取执行结果
String result = ft.get();
System.out.println(result);
}
}
3.4 线程池创建线程
public class MyExecutors implements Runnable{
@Override
public void run() {
System.out.println("MyExecutors...run...");
}
public static void main(String[] args) {
// 创建线程池对象
ExecutorService threadPoll = Executors.newFixedThreadPool(3);
threadPoll.submit(new MyExecutors());
// 关闭线程池
threadPoll.shutdown();
}
}
3.5 线程的run()和start()有什么区别?
start():用来启动线程,通过该线程调用 run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
run():封装了要被线程执行的代码(相对于是普通方法),可以被多次调用。
4. 线程包括哪些状态?
框架
Spring
1. AOP
面向切面编程,作用:减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。(底层:动态代理)
JDK动态代理只能代理实现接口的类或者直接代理接口;对于没有实现接口的类,可以使用CGLIB代理进行代理。Sring框架中默认使用JDK动态代理。
常见的AOP使用场景:
1.记录操作日志
核心:使用AOP中的环绕通知+切点表达式。
2.缓存处理
3.spring中的内置事务
2. Spring中的事务
编程式事务:使用TransactionTemplate来进行实现,对代码的有侵入性,项目中很少用。
声明式事务:其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始前加入一个事务,在执行目标方法之后根据执行情况提交或者事务回滚。
3. Spring中事务失效的场景
3.1 异常捕获处理
原因:事务通知只有捕获到目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉了一场,事务通知无法知晓。
解决:在catch块中添加throw new RuntimeException(e)抛出。
3.2 抛出检查异常
原因:Spring默认只会回滚非检查异常。
解决:配置rollbackFor属性
@Transactional(rollbackFor=Exception.class)//这里也可以写抛出具体的异常
public void update(....)throws FileNotFoundException{
....
}
3.3 非public方法
原因:Spring为方法创建代理、添加事务管理、前提条件都是改方法是public的。
4. Spring的bean的生命周期
5.Spring中的循环引用
三级缓存解决循环依赖
Springboot
1. Springboot自动配置原理
Spring、SpringMVC、Springboot常见注解
1. Spring常见注解
2. SpringMVC常见注解
3. Springboot常见注解
Spring Cloud
1.SpringCloud常见的5大组件
①Eureka:注册中心(阿里巴巴的组件:注册中心/配置中心--Nacos)
②Ribbon:负载均衡
③Feign:远程调用
④Hystrix:服务熔断(阿里巴巴的组件:服务保护--sentinel)
⑤Zuul/Gateway:网关
2.服务注册和发现,SpringCloud是如何实现服务注册发现?
- 以eureka注册中心
- 服务注册:服务提供者把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等
- 服务发现:消费者向eureka拉取服务信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用
- 服务监控:服务提供者会每隔30s向eureka发送心跳,报告健康状况,如果eureka90s没有接收到心跳,则从eureka中剔除
3.Nacos与Eureka的区别?
- Nacos与Eureka的共同点(注册中心)
① 都支持服务注册和服务的拉取
② 都支持服务提供者心跳方式做健康检测
- Nacos与Eureka的区别(注册中心)
①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
② 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
③ Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
- Nacos还支持了配置中心,eureka则只有注册中心,也是选择使用nacos的一个重要原因
MyBatis
1. MyBatis执行流程
①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
②构建会话工厂SqlSessionFactory
③会话工厂创建SqlSession对象(包含了执行SQL语句的所以方法)
④操作数据库的接口,Executor执行器,同时负责查询缓存的维护
⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
⑥输入参数映射
⑦输出参数映射
2. MyBatis是否支持延迟加载
MyBatis支持延迟加载,但默认没有开启。
局部延迟加载:可以在Mapper的映射文件中添加fetchType=‘lazy’就可以开启局部延迟加载了。
全局延迟加载:再MyBatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnable=ture|false,默认是关闭的。
延迟加载的原理:
①使用CGLIB创建目标对象的代理对象
②当调用目标方法时,进入拦截器invoke方法,发现目标方法时null值,执行sql查询
③获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了
3. MyBatis的一级、二级缓存
3.1 一级缓存
是基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session进行flush或者close之后,该Session中的所有Cache就将清空,默认打开一级缓存。
3.2 二级缓存
是基于namespace和mapper的作用域起作用的,不依赖于SQL session,默认也是采用PerpetualCache,HashMap本地缓存。
二级缓存默认是关闭的,需要单独打开,开启方式:
①全局配置文件
<settings>
<setting name="cacheEnable" value="true"/>
</settings>
②映射文件
使用<cache/>标签让当前mapper生效二级缓存。
3.3 二级缓存什么时候会清理缓存中的数据
当某一个作用域(一级缓存Session/二级缓存Namespaces)进行了新增、修改、删除操作后,默认该作用域下select中的缓存都将被clear。
其他
1.#{}和${}的区别
#{}是预编译,可防止SQL注入。
${}是直接拼接在SQL语句中。
Redis
1.缓存
1.1 缓存穿透
查询一个不存在的数据,mysql查询不到数据也不会写入缓存,就会导致每次请求都查询数据库。
解决方案一:缓存空数据,查询返回的数据为空,扔把这个空的结果进行缓存{key:1,value:null}
优点:简单。
确定:消耗内存,可能会发生数据不一致问题。
解决方案二:布隆过滤器
底层主要是先去初始化一个比较大的数组,里面存的二进制0或者1。当一个key来了之后会经过3次hash计算,模于数组长度 找到数据的下标后把数组中原来的0改为1,这样的话三个数组的位置就能标明一个key的存在。查找也是一样的。
优点:内存占用较少,没有多余的key
缺点:实现复杂,存在误判
bitmap(位图):相当于是一个以(bit)位为单位的数组,数组中的每个单元只能存储二进制数0或1。
布隆过滤器的作用:布隆过滤器可以用于检索一个元素是否存在一个集合中。
问题:不存在的数据(假设id3)通过使用相同的hash函数获取的hash值有一定的概率可能会有重复,从而导致误判(误判率)。
解决:数组越小误判率越高,数组越大误判率就越小,但同时带来了更多的内存消耗。设置误判率一般在5%以内。
//第一个参数:布隆过滤器存储的元素个数,第二个参数:误判率
bloomfilter.tryInit(size,0.05);
1.2 缓存击穿
给某一个key设置了过期时间,当key过期时,恰好这个时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。
解决方案一:互斥锁:强一致,性能差
解决方案二:逻辑过期:高可用,性能优
1.3 缓存雪崩
指在同一时间段有大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大的压力。
解决方案:
1.给不同的key的TTL添加随机值
2.利用Redis集群提高服务的可用性(哨兵模式、集群模式)
3.给缓存业务添加降级限流策略(Nginx或spring cloud gateway)
降级可作为系统的保底策略,适用于穿透、击穿、雪崩
4.给业务添加多级缓存