1.自我介绍

2.介绍项目(基本面试官好像都喜欢挖项目)

略,后面补充

3.项目里提到的Mysql,开始展开提问

4.mysql的索引引擎,有什么,之间区别

有InnoDB,MyISAM,Memory

InnoDB:

  • 主键自增
  • 支持外键
  • DML操作支持事务
  • 支持行级锁

MyISAM:

  • 支持表锁,不支持行锁
  • 不支持外键
  • 不支持事务
  • 占用空间小,访问速度快

Memory:

  • 内存存放
  • 支持哈希索引

5.InnoDB采用什么结构存储?为什么?

B+树

  1. 节点排序(用来加快查询速度)
  2. 一个节点多个元素存取(B树高度不会很高)
  3. 叶子节点有指针(可以方便支持全表扫描,范围查找)
  4. 叶子节点冗余(提升范围查找的效率)

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,随便说了个更新频繁)

  1. 使用ConcurrentHashMap实现本地缓存
    缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中线程安全的ConcurrentHashMap,但是要实现缓存,还需要考虑淘汰、最大限制、缓存过期时间淘汰等等功能;

优点是实现简单,不需要引入第三方包,比较适合一些简单的业务场景。缺点是如果需要更多的特性,需要定制化开发,成本会比较高,并且稳定性和可靠性也难以保障。对于比较复杂的场景,建议使用比较稳定的开源工具。

  1. 基于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发生的条件?(面试官说应该有三种,只说上两种)

  1. 年老代(Tenured)被写满;
  2. 持久代(Perm)被写满;
  3. System.gc()被显示调用;
  4. 上一次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.打算实习时间

大约寒假就可以开始

转载美团Java后端实习面经(一二面),已接offer~_牛客博客 (nowcoder.net)