Q1:创建线程池的方法有多少个参数,每个有什么作用?
A:7种,分别是
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, //保持存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler //拒绝策略
Q2:有哪些队列,有什么区别?(有界无界队列,阻塞非阻塞队列区别)
A:
- ArrayBlockingQueue:是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。
- PriorityBlockingQueue:是一个没有边界的队列,它的排序规则和 java.util.PriorityQueue一样。需要注 意,PriorityBlockingQueue中允许插入null对象。
所有插入PriorityBlockingQueue的对象必须实现 java.lang.Comparable接口,队列优先级的排序规则就 是按照我们对这个接口的实现来定义的。另外,我们可以从PriorityBlockingQueue获得一个迭代器Iterator,但这个迭代器并不保证按照优先级顺 序进行迭代。 - SynchronousQueue:SynchronousQueue队列内部仅允许容纳一个元素。当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费。
- ConcurrentLinkedQueue:ConcurrentLinkedQueue 是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue.它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。
ConcurrentLinkedQueue重要方法:
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中这俩个方法没有任何区别)
poll() 和peek() 都是取头元素节点,区别在于前者会删除元素,后者不会。 - **BlockingQueue **:阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列.
Q3:线程池拒绝策略
A:四种
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
- CallerRunsPolicy:由调用线程处理该任务
Q4:创建线程池是自己new还是java自带工具类(有什么区别)
A:推荐手动创建线程池,newSingleThreadExecutor 、newFixedThreadPool两者会提供一个无界的等待队列,当等待队列过多时,会报 OOM 异常,newCachedThreadPool、newScheduledThreadPool两者会无限创建线程,报 OOM 异常
Q5:线程的生命周期
A:
-
新建(new Thread):当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
-
就绪(runnable):线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源
-
运行(running):线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
-
堵塞(blocked):由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复) -
死亡(dead):当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
Q6:wait和sleep的区别(谁会释放锁)
A:wait会释放锁
Q7:volite的作用
A:
- 保证内存可见性
- 禁止指令重排序
- volatile并不能保证原子性
即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。
volatile适用于:对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的
它是被设计用来修饰不同线程访问和修改的变量
Q8:netty的实现模型
Q9:IO复用模型(怎么实现多路复用的)
Q10:tomcat为什么打破双亲加载机制?
Q11:TreeMap的实现(元素添加移除的实现)
Q12:HashMap在多线程环境会出什么问题?
A:
-
丢失元素
1.当多线程同时put值的时候,若发生hash碰撞,可能多个元素都落在链表的头部,从而造成元素覆盖(hashcode相同而eques值不同的元素)
列如:线程A put一个元素a ,线程B put一个元素b,a,b 发生hansh碰撞,本应该在map是链表的形式存在,但是可能线程A和线程B同时put到链表的第一个位置,从而后来者覆盖前者元素造成元素丢失。 -
put 造成链表形成闭环,get的时候出现死循环(jdk8已经解决该问题)
该情况是出现在多线线程操作map扩容时会发生
Q13:redis的雪崩,击穿
A:
- 击穿:指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的。
如果数据库数据量大并且是高并发的情况下那么就可能会造成数据库压力过大而崩溃
注意: 这里指的是单个key发生高并发!!!
解决方案:
- 通过synchronized+双重检查机制:某个key只让一个线程查询,阻塞其它线程
在同步块中,继续判断检查,保证不存在,才去查DB。
缺点: 会阻塞其它线程 - 设置value永不过期
这种方式可以说是最可靠的,最安全的但是占空间,内存消耗大,并且不能保持数据最新 这个需要根据具体的业务逻辑来做 - 使用互斥锁(mutex key)
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
缺点:
1代码复杂度增大
2 存在死锁的风险
3.存在线程池阻塞的风险
- 雪崩
雪崩指的是多个key查询并且出现高并发,缓存中失效或者查不到,然后都去db查询,从而导致db压力突然飙升,从而崩溃。
出现原因: 1 key同时失效,2 redis本身崩溃了
方案:
1在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。(跟击穿的第一个方案类似,但是这样是避免不了其它key去查数据库,只能减少查询的次数)
2可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存
3不同的key,设置不同的过期时间,具体值可以根据业务决定,让缓存失效的时间点尽量均匀
4做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。(这种方式复杂点) - 穿透
一般是出现这种情况是因为恶意频繁查询才会对系统造成很大的问题: key缓存并且数据库不存在,所以每次查询都会查询数据库从而导致数据库崩溃。
Q14:redis和db的数据一致性
A:
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应
- 更新的时候,先更新数据库,然后再删除缓存。
Q15:redis的数据类型
A:string、list、set、hash、sort set
Q16:redis的list怎么实现的
Q17:redis有序集合的实现(跳表)
Q18:redis内存碎片率(内存碎片)
Q19:redis处理过期key的策略
- 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
- 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
- 当前已用内存超过maxmemory限定时,触发主动清理策略
Q20:redis两种持久化策略的区别
- rdb:快照形式是直接把内存中的数据保存到一个dump文件中,定时保存,保存策略
- aof:把所有的对redis的服务器进行修改的命令都存到一个文件里,命令的集合
默认情况下,是快照rdb的持久化方式,将内存中的数据以快照的方式写入二进制文件中,默认的文件名是dump.rdb
这种方式不能完全保证数据持久化,因为是定时保存,所以当redis服务down掉,就会丢失一部分数据,而且数据量大,写操作多的情况下,会引起大量的磁盘IO操作,会影响性能
Q21:redis持久化的时候可以写入吗
Q22:redis线程模型
Q23:redis内存回收策略
Redis可以通过maxmemory设置最大可用内存,如果数据量已经达到maxmemory的限制,不加大maxmemory,就必须将现有数据所占有的一部分内存回收,才能响应写入请求。
当数据量达到maxmemory上限,对于内存回收,Redis有几种不同的策略。
1.noeviction
新写入请求会报错Redis不主动回收内存空间,新写入请求会报错
2.allkeys-lru
针对当前所有数据,根据LRU算法来回收最近最少使用的key。
3.volatil-lru
针对设置了过期时间的key,根据LRU算法来税后最近最少使用的。
4.allkeys-randon
针对所有数据,随机回收一部分key。
5.volatil-lru
针对设置了过期时间的key,随机回收一部分key。
6.volatil-ttl
针对设置了过期时间的key,优先回收那些当前更快达到过期时间的key。
当volatil-lru,volatil-random,volatil-ttl没有找到合适的key来回收,则这些策略就跟noeviction一样。
Q24:redis事务
Q25:redis主从集群和哨兵集群区别
Q26:redis主节点挂了会怎么样
Q27:redis选举主节点的方法
Q28:redis主从集群的缺点
- 故障恢复复杂,如果没有 RedisHA 系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,同时需要通知业务方变更配置,并且需要让其它从库节点去复制新主库节点,整个过程需要人为干预,比较繁琐;
- 主库的写能力受到单机的限制,可以考虑分片;
- 主库的存储能力受到单机的限制,可以考虑 Pika;
- 原生复制的弊端在早期的版本中也会比较突出,如:Redis 复制中断后,Slave 会发起 psync,此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时可能会造成毫秒或秒级的卡顿;又由于 COW 机制,导致极端情况下的主库内存溢出,程序异常退出或宕机;主库节点生成备份文件导致服务器磁盘 IO 和 CPU(压缩)资源消耗;发送数 GB 大小的备份文件导致服务器出口带宽暴增,阻塞请求,建议升级到最新版本。
- 最大的不足就是主从模式不具备自动容错和恢复功能,主节点故障,集群则无法进行工作,可用性比较低,从节点升主节点需要人工手动干预。
Q29:redis横向扩展
Q30:redis从节点挂了重启后数据不同步怎么处理的
首先会从主节点获取rdb快照,然后sync到自己节点上,sync期间主节点接收到的操作请求会在sync结束后统一的同步到从节点
Q31:springboot三级缓存
我们在之前介绍Bean的生命周期时说过,spring 中 bean的实例化过程,并非只是调用构造方法。除去spring框架本身提供的一些钩子或扩展方法,简单分成下面三个核心方法:
Spring在创建Bean的过程中分为三步
- 实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法,简单理解就是new了一个对象。
- 属性注入,对应方法:AbstractAutowireCapableBeanFactory的populateBean方法,为实例化中new出来的对象填充属性和注入依赖。
- 初始化,对应方法:AbstractAutowireCapableBeanFactory的initializeBean,执行aware接口中的方法,初始化方法,完成AOP代理。
从单例Bean的初始化来看,主要可能发生循环依赖的环节就在第二步populate。值得注意的是,基于构造方法注入的方式,其实是将第一步和第二步同时进行,因此马上就抛出错误。而spring通过基于属性注入的方式,是否有其他特殊的处理呢,我们这时候就要提到spring的三级缓存:
缓存 | 说明 |
---|---|
singletonObjects | 第一级缓存,存放可用的完全初始化,成品的Bean。 |
earlySingletonObjects | 第二级缓存,存放半成品的Bean,半成品的Bean是已创建对象,但是未注入属性和初始化。用以解决循环依赖 |
singletonFactories | 第三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中。用以解决循环依赖。如果Bean存在AOP的话,返回的是AOP的代理对象。 |
Q32:springboot启用配置的条件有哪些
常用的条件注解
@Conditional 依赖的条件
@ConditionalOnBean 在某个Bean存在的条件下
@ConditionalOnMissingBean 在某个Bean不存在的条件下
@ConditionalOnClass 在某个Class存在的条件下
@ConditionalOnMissingClass 在某个Class不存在的条件下
Q33:springboot的bean的生命周期
Bean生命周期一般有下面的四个阶段:
-
Bean的定义
-
Bean的初始化
-
Bean的生存期
-
Bean的销毁
Q34:springboot的设计模式
简单工厂
工厂方法(Factory Method)
单例模式(Singleton)
适配器(Adapter)
包装器(Decorator)
代理(Proxy)
观察者(Observer)
策略(Strategy)
模板方法(Template Method)
…
Q35:mysql索引实现(为什么选择B+树,为什么不使用Hash表,为啥不用二叉树)
Q36:mysql的联合索引怎么实现的
Q37:mysql回表和覆盖索引
Q38:mysql索引常见的失效原因
索引设计的几个建议: