宗蔚 2024-03-20
后端
1、不同的数据库如何实现分页查询
2、有多少种索引以及应用场景
主键索引 唯一索引 普通索引 组合索引 全文索引
3、索引在数据库的底层实现
B+ 树
非叶子节点存放的是索引,叶子节点存放的是数据。
4、like 在不同数据库的实现原理
对每个字段进行匹配。
5、连接函数的实现原理
6、怎么实现数据库兼容
7、JVM 的内存模型
8、导致内存溢出的原因有哪些? 分别是导致什么内存溢出(栈内存溢出,堆内存溢出)
9、如何控制线程池(线程池的参数设置)如果任务塞满阻塞队列线程池会怎么处理。
线程池一般通过创建 ThreadPoolExecutor
来创建一个线程池,在创建的时候可以传入 7 个参数,分别是
- 核心线程的数量
- 最大线程的数量
- 超时时间
- 超时时间的单位 秒,毫秒, 分等
- 线程工厂,用来创建线程的工厂
- 阻塞队列,当工作的线程达到最大线程数时,会把新加的任务添加到阻塞队列里面
- 拒绝策略,阻塞队列满了,会根据拒绝策略来判断新家的任务是如何操作的。
- 直接丢弃
- 报错
- 丢弃阻塞队列的第一个任务之后再插入
- 在当前线程执行。
如果允许的任务数达到了最大线程数,此时,新添加一个任务到线程池,会将任务添加到阻塞队列(工作队列),如果阻塞队列已满,就在判断一下当前 运行的任务是否达到最大线程数,没有达到就会直接新建一个线程来运行这个任务,否则根据拒绝策略来对任务进行处理。
不建议使用 Executors
来创建线程池,因为在某种情况下会导致内存溢出。(Executors 部分方法使用的是无界队列)
线程池的运行过程
- 刚开始运行时,线程池是空的。
- 一个任务进来,检查池中的线程数量,是否达到
corePoolSize
, 如果没有达到,则创建线程,执行任务。 - 任务执行完成后,线程不会销毁,而是阻塞,等待下一个任务。
- 又进来一个任务,不是直接使用阻塞的线程,而是检查线程池中的线程数大小,是否达到
corePoolSize
,如果没有达到,则继续创建新的线程,来执行新的任务,如此往复, 直到线程池中的线程数达到corePoolSize
,此时停止创建新的线程。 - 此时,又来新的任务,会选择线程池中阻塞等待的线程来执行任务,有一个任务进来,唤醒一个线程来执行这个任务,处理完之后,再次阻塞,尝试在
workQueue
上获取 下一个任务,如果线程池中没有可唤醒的线程, 则任务进入workQueue
,排队等待。 - 如果队列是无界队列,比如
LinkedBlockingQueue
,默认最大容量为Integer.Max
,接近于无界, 可用无限制的接收任务,如果任务是有界队列,比如ArrayBlockingQueue
,可限定队列大小,当线程池中的线程来不及处理, 然后,所有的任务都进入队列,队列的任务数也达到限定大小,此时,再来新的任务,就会入队失败,然后,就会再次尝试在线程池创建线程, 直到线程数达到maximumPoolSize
,停止创建线程。 - 此时,队列满了,新的任务无法入队。创建的线程数也达到了
maximumPoolSize
,无法再创建新的线程,此时,就会reject
,使用拒绝策略RejectedExecutionHandler
,不让继续提交任务,默认的是AbortPolicy
策略,拒绝,并抛出异常。 - 超出
corePoolSize
数创建的那部分线程,是跟空闲时间keepAliveTime
相关的,如果超过 keepAliveTime 时间还获取不到 任务,线程会被销毁,自动释放掉。
为什么不建议使用 Executors 创建线程,而使用 ThreadPoolExecute 实现类来创建线程?
Executors
创建的 FixedThreadPool
和 SingleThreadExecutor
使
用的是 LinkedBlockingQueue
阻塞队列,默认大小是 Integer.MAX_VALUE
,
可以无限制的将任务添加到队列中。CachedThreadPool
允许创建线程的数量为
Integer.MAX_VALUE
,可能会创建大量的线程。在极端的情况下会导致
JVM OOM
,系统就挂了。
线程池调优
- 高并发、任务执行时间短,此类任务可以充分利用 CPU,尽可能减少上下文切换,线程池的线程数可以设置为
CPU core + 1
- 并发不高,任务执行时间长
- IO 密集
2 * CPU core
- CPU 密集
CPU core + 1
- IO 密集
- 高并发、业务执行时间长 考虑拆分、解耦、部分数据缓存,增加服务器。
tasks
每秒的任务数
taskCost
每个任务花费的时间
responseTime
系统容忍的最大响应时间
corePoolSize = tasks * taskCost / responseTime
实际场景
- 任务数多但资源占用不大: 电商平台的消息推送或短信通知
BlockingQueue queue = newArrayBlockingQueue<>(4096);
ThreadPoolExecutor executor = newThreadPoolExecutor(16, 16, 0, TimeUnit.SECONDS, queue);
- 任务数不多但资源占用大: 日志收集、图片流压缩或批量订单处理等场景
BlockingQueue queue = newArrayBlockingQueue<>(512);
ThreadPoolExecutor executor =newThreadPoolExecutor(16, 64, 30, TimeUnit.SECONDS, queue);
- 极端情况,任务多,资源占用大
BlockingQueue queue = newSynchronousQueue<>();
ThreadPoolExecutor executor =newThreadPoolExecutor(64, 64, 0, TimeUnit.SECONDS, queue);