这是携程的Java二面,主要问的还是八股。
📣二面50min
题目:
0. 自我介绍:
使用多线程可能存在的问题:
线程池原理:
聊聊ThreadLocal(概念 + 一些应用举例 + 常见的内存泄露问题):
JVM内存模型和垃圾回收:
用到过内存分析工具吗:
使用索引能带来什么好处,你项目中是怎么实现的:
索引底常见的数据结构,MyISAM引擎和InnoDB引擎用的是哪种:
聚簇索引和非聚簇索引:
最左前缀匹配原则:
造成索引失效的常见原因你知道哪些,项目中遇到过索引失效问题吗:
如果有一条SQL语句执行的很慢,如何进行优化:
项目中是如何使用ES的:
ES检索较快的原因,为什么MySQL不行:
讲一下倒排索引:
手写一个生产者消费队列:
答案:
1️⃣使用多线程可能存在的问题:
🔑:
并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。
详细见大佬博客:使用多线程可能会遇到的问题 - qudehu - 博客园 (cnblogs.com)
2️⃣线程池原理:
🔑:
顾名思义,线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。
一个线程提交到线程池的处理流程如下图:
3️⃣聊聊ThreadLocal(概念 + 一些应用举例 + 常见的内存泄露问题):
🔑:
概念:
ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
例子:
两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。
常见的内存泄露问题:
由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。
但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
正确使用方法:
每次使用完ThreadLocal都调用它的remove()方法清除数据
将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。
可以看大佬文章面试必备:ThreadLocal详解 - 掘金 (juejin.cn)
4️⃣JVM内存模型和垃圾回收:
🔑:
内存模型:
Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是:
1. 程序计数器
2. Java虚拟机栈
3. 本地方法栈
4. 堆
5. 方法区
图如下:
垃圾回收机制:
这个内容有点庞大,直接看大佬解答: 【JVM】JVM垃圾回收机制_chenyi丶的博客-CSDN博客
5️⃣用到过内存分析工具吗:
🔑:
jmap,jhat和dump.
6️⃣使用索引能带来什么好处,你项目中是怎么实现的:
🔑:
索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。
索引的作用就相当于书的目录。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引的优缺点:
优点 :
使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点 :
创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
索引需要使用物理文件存储,也会耗费一定空间。
使用索引一定能提高查询性能吗?
大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
7️⃣索引底常见的数据结构,MyISAM引擎和InnoDB引擎用的是哪种:
🔑:
索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam,都使用了 B+树作为索引结构。
8️⃣聚簇索引和非聚簇索引:
🔑:
聚簇索引即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
在 MySQL 中,InnoDB 引擎的表的 .ibd文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
非聚簇索引即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
9️⃣最左前缀匹配原则:
🔑:
最左前缀匹配原则指的是,在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 >、<)才会停止匹配。对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
🔟造成索引失效的常见原因:
🔑:
索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:
使用 SELECT * 进行查询;(新手常犯)
创建了组合索引,但查询条件未遵守最左匹配原则;
在索引列上进行计算、函数、类型转换等操作;
以 % 开头的 LIKE 查询比如 like '%abc';
查询条件中使用 or,且 or 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
还有很多
1️⃣1️⃣如果有一条SQL语句执行的很慢,如何进行优化:
🔑:
慢sql如何优化
对于MYSQL慢sql语句的优化,我们也可以分几个方面来进行分析(基本覆盖全面啦):
面试从这几方面考虑:索引+sql语句+数据库结构优化+优化器优化+架构优化。
索引
1、尽量覆盖索引,5.6支持索引下推
2、组合索引符合最左匹配原则
3、避免索引失效
4、再写多读少的场景下,可以选择普通索引而不要唯一索引
更新时,普通索引可以使用change buffer进行优化,减少磁盘IO,将更新操作记录到change bufer,等查询来了将数据读到内存再进行修改.
5、索引建立原则(一般建在where和order by,基数要大,区分度要高,不要过度索引,外键建索引)
sql语句
1、分页查询优化
该方案适用于主键自增的表,可以把Limit查询转换成某个位置的查询。
select * from tb_sku where id>20000 limit 10;
2、优化insert语句
多条插入语句写成一条
在事务中插数据
数据有序插入(主键索引)
数据库结构优化
1、将字段多的表分解成多个表
有些字段使用频率高,有些低,数据量大时,会由于使用频率低的存在而变慢,可以考虑分开。
2、对于经常联合查询的表,可以考虑建立中间表
优化器优化
1、优化器使用MRR
原理:MRR 【Multi-Range Read】将ID或键值读到buffer排序,通过把「随机磁盘读」,转化为「顺序磁盘读」,减少磁盘IO,从而提高了索引查询的性能。
mysql >set optimizer_switch='mrr=on';
explain 查看 Extra多了一个MRR
explain select * from stu where age between 10 and 20;
对于 Myisam,在去磁盘获取完整数据之前,会先按照 rowid 排好序,再去顺序的读取磁盘。
对于 Innodb,则会按照聚簇索引键值排好序,再顺序的读取聚簇索引。
磁盘预读:请求一页的数据时,可以把后面几页的数据也一起返回,放到数据缓冲池中,这样如果下次刚好需要下一页的数据,就不再需要到磁盘读取(局部性原理)
索引本身就是为了减少磁盘 IO,加快查询,而 MRR,则是把索引减少磁盘 IO 的作用,进一步放大
架构优化
读/写分离(主库写,从库读)
总结:
1先设置慢查询(my.ini或数据库命令)
2分析慢查询日志
3定位低效率sql(show processlist)
4 explain分析执行计划(是否索引失效,用到索引没,用了哪些)
5 优化(索引+sql语句+数据库结构优化+优化器优化+架构优化)
1️⃣2️⃣项目中是如何使用ES(ElasticSearch)的:
🔑:
不熟,传送带 ElasticSearch在项目中具体怎么用?
1️⃣3️⃣ES检索较快的原因,为什么MySQL不行:
🔑:
ES的特点:
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎。
优势:
1)分布式的文件存储,每个字段都被索引且可用于搜索。
2)分布式的实时分析搜索引擎,海量数据下近实时秒级响应。
3)简单的restful api,天生的兼容多语言开发。
4)易扩展,处理PB级结构化或非结构化数据。(pb指petabyte,1PB=1024TB)
12cvx
ES比MySQL快的原因:
1)基于分词后的全文检索:例如select * from test where name like '%张三%',对于mysql来说,因为索引失效,会进行全表检索;对es而言分词后,每个字都可以利用FST高速找到倒排索引的位置,并迅速获取文档id列表,大大的提升了性能,减少了磁盘IO。
2)精确检索:进行精确检索,有些时候可能mysql要快一些,当mysql的非聚合索引引用上了聚合索引,无需回表,则速度上可能更快;es还是通过FST找到倒排索引的位置比获取文档id列表,再根据文档id获取文档并根据相关度进行排序。但是es还有个优势,就是es即天然的分布式能够在大量数据搜索时可以通过分片降低检索规模,并且可以通过并行检索提升效率,用filter时,更是可以直接跳过检索直接走缓存。
1️⃣4️⃣讲一下倒排索引:
倒排索引,也是索引。
索引,初衷都是为了快速检索到你要的数据。
每种数据库都有自己要解决的问题(或者说擅长的领域),对应的就有自己的数据结构,而不同的使用场景和数据结构,需要用不同的索引,才能起到最大化加快查询的目的。
对 Mysql 来说,是 B+ 树,对 Elasticsearch/Lucene 来说,是倒排索引。
Elasticsearch 是建立在全文搜索引擎库 Lucene 基础上的搜索引擎,它隐藏了 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API,不过掩盖不了它底层也是 Lucene 的事实。
Elasticsearch 的倒排索引,其实就是 Lucene 的倒排索引。
拓展:
问:Lucene 为什么不用 b+ 树来搜索数据?
答:b+树主要设计目的是减少搜索时访问磁盘的次数,而Lucene等搜索引擎设计的时候,追求的目标是倒排压缩率&倒排解压速度&倒排Bool运算速度。取倒排到内存运算的时候,是连续读取,时间开销和倒排的大小有关系,所以并不适合用b+数。
问:Mysql 为什么不用 倒排索引来检索数据?
答:同理Mysql等数据库使用索引的目的是快速定位某一行数据,若使用倒排这种线性化的数据结构存储数据,其查找的时候访问磁盘的次数会远大于使用b+的数据库。
总结:现有的pc架构 &业务需求决定了什么时候使用什么数据结构。
1️⃣5️⃣手写一个生产者消费队列:
这种设计模式需要满足以下三点要求:
(1)生产者生产数据到缓冲区中,消费者从缓冲区中取数据。
(2)如果缓冲区已经满了,则生产者线程阻塞;
(3)如果缓冲区为空,那么消费者线程阻塞。
编写之前分析:
(1)定义一个缓存队列,选择一个集合当做缓存,给予缓存上限,缓存队列只有两种行为(生产数据和消费数据);
(2)定义一个生产者线程,调用缓存队列中的生产行为;
(3)定义一个消费者线程,调用缓存队列中的消费行为;
有3种方式:
双向链表LinkedHashMap和synchronized结合
双向链表LinkedHashMap和lock结合
直接使用阻塞队列BlockingQueue(easy)
下面是第三中方法的代码,建议背诵!建议背诵!建议背诵!
/**
* 公共缓存队列
* 只做两件事:(1)生产;(2)消费
*/
public class PublicQueue<T> {
private BlockingDeque<T> blockingDeque = new LinkedBlockingDeque<>(50);//缓冲区
public void add(T msg){
try {
blockingDeque.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产一个产品,当前商品角标为:"+"===文本为:"+msg);
}
public T remove(){
T t = null;
try {
t = blockingDeque.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费一个产品,当前商品角标为:"+"===文本为:"+t);
return t;
}
}