1.自我介绍
略
2.介绍项目(基本面试官好像都喜欢挖项目)
略,后面补充
3.项目里提到的Mysql,开始展开提问
略
4.mysql的索引引擎,有什么,之间区别
有InnoDB,MyISAM,Memory
InnoDB:
- 主键自增
- 支持外键
- DML操作支持事务
- 支持行级锁
MyISAM:
- 支持表锁,不支持行锁
- 不支持外键
- 不支持事务
- 占用空间小,访问速度快
Memory:
- 内存存放
- 支持哈希索引
5.InnoDB采用什么结构存储?为什么?
B+树
- 节点排序(用来加快查询速度)
- 一个节点多个元素存取(B树高度不会很高)
- 叶子节点有指针(可以方便支持全表扫描,范围查找)
- 叶子节点冗余(提升范围查找的效率)
6.B+树存储怎么能确定高度?
InnoDB最小存储单位是页,叶子节点和非叶子节点最小单位都是页,页大小Mysql 默认设定16384字节,约为16KB。我们假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节
我们一个页中能存放多少这样的索引元素,其实就代表有多少指针,即16384/14=1170;
1 假设一行记录的数据大小为1k,实际上现在很多互联网业务数据记录大小通常就是1K左右高度为2的B+树能存放1170×16=18720
高度为3的B+树能存放1170×1170×16 = 21902400
7.最左匹配(给举个例子,说出能否用索引)
略
8.mysql中行锁具体怎么锁(当时理解错了,答得MVCC,面试官也是耐心听完了,然后问我,那行锁呢?就很尴尬)
用记录锁、间隙锁、临键锁(都是排他锁)
(1)记录锁(Record): 通过主键或唯一索引加锁,锁定某行记录,锁定的是已存在的记录
(2)间隙锁(Gap): 锁定的是索引记录中的间隔,锁定的是未存在记录的区间。
(3)临键锁(Next-key): 既包含已存在的记录,又包含未存在记录的区间(记录锁+间隙锁)
锁的触发条件
1.记录锁(Record)触发条件: 查询的条件中只包含表中存在的记录
2.间隙锁(Gap)触发条件: 查询的条件中不包含表中任何记录
3.临键锁(Next-key)触发条件: 查询的条件中既包含表中存在的记录,也包含表中不存在的记录
9.redis在应用时怎么保证数据一致性的(问的是项目里用的时候)
采用延时双删策略
(1)先淘汰缓存
(2)再写数据库(这两步和原来一样)
(3)休眠1秒,再次淘汰缓存先更新数据库,再删除缓存(推荐)
10.有没有想过用本地缓存,不用redis(当时不了解memcache,随便说了个更新频繁)
- 使用ConcurrentHashMap实现本地缓存
缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中线程安全的ConcurrentHashMap,但是要实现缓存,还需要考虑淘汰、最大限制、缓存过期时间淘汰等等功能;优点是实现简单,不需要引入第三方包,比较适合一些简单的业务场景。缺点是如果需要更多的特性,需要定制化开发,成本会比较高,并且稳定性和可靠性也难以保障。对于比较复杂的场景,建议使用比较稳定的开源工具。
- 基于Guava Cache实现本地缓存
Guava是Google团队开源的一款 Java 核心增强库,包含集合、并发原语、缓存、IO、反射等工具箱,性能和稳定性上都有保障,应用十分广泛。Guava Cache支持很多特性:支持最大容量限制
支持两种过期删除策略(插入时间和访问时间)
支持简单的统计功能
基于LRU算法实现
11.接10问,redis一般存哪种数据结构(存的list,所以本地缓存用不了)
存String
12.想没想过redis宕机怎么办?
如果是一台机器,利用AOF和RDB机制进行redis数据恢复
从机宕机:
- 只要把从的redis重新启动,再和主的进行连接就可以
- 如果从redis上面做数据的持久化,可以直接连接到主的上面,只要实现增量备份。
主机宕机:
- 先把从的redis升级为主的redis. 执行slave of one命令
原来的主的可以重新启动,作为从的redis, 连接到主的redis上面做主从复制。
可以使用Redis 提供哨兵 机制来简化上面的操作。
13.了不了解垃圾回收器,说一下CMS的过程?
- 初始标记
- 并发标记
- 重新标记
- 并发清除
14.FullGC发生的条件?(面试官说应该有三种,只说上两种)
- 年老代(Tenured)被写满;
- 持久代(Perm)被写满;
- System.gc()被显示调用;
- 上一次GC之后Heap的各域分配策略动态变化;
15.了解线程池吗?主要参数?
了解
corePoolSize核心线程数
maximumPoolSize最大线程数
keepAliveTime线程空闲时间
unit时间单位
workQueue阻塞队列
threadFactory线程工厂
handler任务拒绝处理器
16.线程池的执行过程?
当我们利用线程池执行任务时:
1.如果此时线程池中的线程数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
⒉.如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
3.如果此时线程池中的线程数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4.如果此时线程池中的线程数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
5.当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数
17.可重入锁是怎么实现的?
利用AQS实现(AbstractQueueSynchronize)抽象队列同步器
18.由于17问说不知道,提醒出了18问AQS知道吗?(答完之后,面试官说这就是可重入锁实现)
AQS 中有两个重要的东西,一个以Node为节点实现的链表的队列(CHL队列),还有一个STATE标志,并且通过CAS来改变它的值。
CLH队列:
链表结构,在头尾结点中,需要特别指出的是头结点是一个空对象结点,无任何意义,即傀儡结点;
每一个Node结点都维护了一个指向前驱的指针和指向后驱的指针,结点与结点之间相互关联构成链表;
入队在尾,出队在头,出队后需要激活该出队结点的后继结点,若后继结点为空或后继结点waitStatus>0,则从队尾向前遍历取waitStatus<0的触发阻塞唤醒;
队列中节点状态值(waitStatus,只能为以下值)
1
2
3
4
5
6
7
8 //常量:表示节点的线程是已被取消的
static final int CANCELLED = 1;
//常量:表示当前节点的后继节点的线程需要被唤醒
static final int SIGNAL = -1;
//常量:表示线程正在等待某个条件
static final int CONDITION = -2;
//常量:表示下一个共享模式的节点应该无条件的传播下去
static final int PROPAGATE = -3;
19.CAS是什么
CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。
CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
ps:
1
2
3 问题:
①.CAS容易造成ABA问题。一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在java5中,已经提供了AtomicStampedReference来解决问题。
②.CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。
反问
20.打算实习时间
大约寒假就可以开始