1.假如有两个线程共同操作数据库,以乐观锁的角度考虑,怎么确保不会发生并发问题?
PS:考点是CAS,比较并替换。CAS中有三个值,内存中的值,新值,旧值。
假如内存中的值是2000,要进行--操作,A,B两个线程分别从主内存中拉去数据,当A线程进行--操作,新值变成了1999,旧值与主内存中的值一致,将新值替换掉主内存中的值,此时主内存值为1999。当B线程进行--操作,新值也是1999,比较主内存值1999与旧值2000不一致,拉去主内存值,在--,此时旧值变成了1999,新值变成了1998……
依次类推,AB线程共同操作共享资源,数据也不会出现并发问题。
2. redis缓存击穿、穿透、雪崩
击穿:数据库中有,缓存中没有的数据。
解决办法:
- 设置热点数据永不过期
- 加互斥锁
穿透:缓存和数据库中都没有的数据。
解决办法:
- 将value的值赋为null
- 在接口层增加校验
- 布隆过滤器:快速检索一个元素是否在集合中,不存在的一定能检索到
雪崩:同一时间大量数据请求数据库,导致数据库压力大而宕机。
解决办法:
- 过期时间设置随机,避免同一时间多个缓存同时过期
- 设置热点数据永不过期
- 使用锁或队列的方式保证不会有大量线程对数据库一次性进行读写
3.数据库优化
一方面从sql优化、索引优化入手,另一方面从数据库的表设计方面入手。
对于sql优化、索引优化。具体包括以下几点:
- 利用好索引,避免全表扫描
- 优化子查询,使用inner join代替子查询
- 减少无效数据查询,使用select字段名来代替select *
对于表设计方面包括:
- 尽量使用固定长度的字段
- 限制字段长度
- 分库分表
从IO角度考虑,还可以增加缓冲区
索引包括哪些?
- 普通索引
- 唯一索引
- 主键索引
- 联合索引
导致索引失效的几种情况:
- 遇到null值
- 模糊查询 xxx%
- 使用or
- 最左匹配原则
- where 1=1
- =前边有表达式或函数
- 使用!=
- 类型隐式转化,比如a是varchar类型,sql中写a=1这样
4.redis过期键的删除策论有哪些?
定时删除
通过使用定时器来删除,保证过期键尽可能的删除,并释放过期键占用的内存。
对内存友好,对CPU不友好。
惰性删除
获取键时对键进行过期检测,不会在删除其他无关过期键花费CPU
对CPU友好,对内存不友好
定期删除
定时删除和惰性删除的一种折中策略,每隔一段时间执行一次删除过期键操作
5.快速失败中,为什么在foreach的过程中,使用remove或者改变集合长度会抛异常
在使用迭代器Iterator的时候,调用集合本身的方法。多线程会导致数据不安全。
在循环或迭代中,首先会创建一个迭代实例,这个迭代实例的expectedModCount赋值为集合的modCount,每当迭代器使用next()获得下一个之前,会检测 modCount 变量与expectedModCount 值是否相等,相等的话就返回遍历;否则就抛出异常【ConcurrentModificationException】,终⽌遍历。
循环中添加或删除元素,是直接调用集合的add,remove方法【导致了modCount增加或减少】,但这些方法不会修改迭代实例中的expectedModCount,导致在迭代实例中expectedModCount 与 modCount的值不相等,抛出ConcurrentModificationException异常
6.mybatis分页是如何实现的?
通过page对象作为分页依据
通过count作为查询总条数限制
对原有sql通过limit进行分页
7.like %和like _的区别
%代表任意多个字符;_代表任意一个字符。
8.为什么redis最常用的数据类型是String
因为String类型的数据结构简单,存储空间占据小。我们知道redis缓存中的数据是要存到内存中的,而内存的空间毕竟有有限的,所以能用String时,尽量用String
9.POI和easyExcel的区别
POI是将内容先写到内存,再加载到文件;easyExcel是读一行解析一行
10.java的内存模型
java的内存模型分为工作内存和主内存。
工作内存是线程私有的,主内存是线程共享的。
当线程工作时,需要从主内存中拉取到自己的工作内存,在工作内存中读取和修改,当当前线程修改了共享变量后,其他线程不可见。这就导致了“内存不可见”问题。
上升到CPU是各级缓存与主内存不可见问题,采用缓存一致性来解决这个问题
java采用内存屏障来解决,即synchronized,volidate。volidate对线程共享资源修改后会立即同步到主内存,并且其他线程共享变量副本失效,使用时重新从主内存中拉取。
11. 三种线程池的适用场景
线程池的作用:
重复利用现有线程,避免线程多次创建与销毁。
fixedThreadPool:固定数量线程,核心线程数=最大线程数,阻塞队列是int最大值
适用于cpu计算密集的情况(cpu是调度线程的,cpu密集可以理解为程序一直在计算),因为cpu密集,再去创建线程已经顾不上了,所以固定数量的就足够了。
cachedThreadPool:可扩容数量线程池,核心线程数0,最大线程数为int的最大值
用到多少线程,创建多少。阻塞队列是0
适用于io密集的场景,io密集就会产生io阻塞,那么这是cpu就有空闲,这是就可以创建线程,以保证cpu尽可能多的使用。
singleThreadExecutor:核心线程数=最大线程数=1 单个线程,但是有阻塞队列
12.多线程的执行原理/拒绝策略的流程
线程池中有几个重要的参数:核心线程数,最大线程数,阻塞队列。
比如一个线程池的核心线程数是5,最大线程数是10,阻塞队列是1024。
首先核心线程数来处理任务,当任务数增加放入阻塞队列,其他任务等待核心线程处理完,当阻塞队列满了,就会判断核心线程数是否达到了最大线程数,如果没有,那么就会创建新线程到最大。如果这是阻塞队列满了,那么再来任务的时候,就会触发拒绝策略。
13. 接口权限控制
首次登录的时候会生成一个token,前端拿到token放到请求头中,以后每次的请求都需要携带token,后端判断前端带过来的与数据库或者缓存中的token是否一致,一致的话代表登录过了。
我们利用spring的aop在每个接口前建一个切面,切面统一执行判断token,记得过滤掉登录接口。