面试官:第一轮面试开始。首先,说说 Java 中的多线程有哪些实现方式?
王铁牛:嗯,这个简单,有继承 Thread 类和实现 Runnable 接口这两种方式。
面试官:不错,回答正确。那线程池的核心参数都有哪些?
王铁牛:有 corePoolSize、maximumPoolSize、keepAliveTime、unit 和 workQueue。
面试官:很好。再问个,HashMap 在 JDK1.8 之后做了哪些优化?
王铁牛:这个嘛……它在链表长度大于 8 且数组长度大于 64 时会将链表转换为红黑树,还有一些其他的,嗯……想不太起来了。
面试官:第二轮面试。讲讲 JVM 的内存模型都包含哪些部分?
王铁牛:有堆、栈、方法区这些。
面试官:Spring 框架中依赖注入有几种方式?
王铁牛:有构造器注入、setter 注入这些。
面试官:MyBatis 的缓存机制了解吗?
王铁牛:不太清楚,瞎答一个,好像有一级缓存和二级缓存吧。
面试官:最后一轮面试。Dubbo 的集群容错策略有哪些?
王铁牛:呃……不太记得全了,好像有 failover、failfast 这些。
面试官:RabbitMq 的消息确认机制是怎样的?
王铁牛:这个真不会,随便说一个吧,应该是发送端确认和接收端确认吧。
面试官:xxl-job 的调度中心是如何实现任务调度的?
王铁牛:完全不懂,乱说一通。
面试官:好了,面试结束,回去等通知吧。
答案:
- Java 多线程实现方式:
- 继承 Thread 类:通过继承 Thread 类并重写 run 方法来创建线程。
- 实现 Runnable 接口:实现 Runnable 接口的 run 方法,然后将其作为参数传递给 Thread 类的构造函数来创建线程。
- 线程池核心参数:
- corePoolSize:线程池的核心线程数,当提交的任务数小于 corePoolSize 时,会创建新线程来执行任务。
- maximumPoolSize:线程池允许的最大线程数,当提交的任务数大于 corePoolSize 且任务队列已满时,会创建新线程来执行任务,直到线程数达到 maximumPoolSize。
- keepAliveTime:线程池中非核心线程的存活时间,当线程数大于 corePoolSize 时,多余的线程在空闲时间超过 keepAliveTime 后会被销毁。
- unit:keepAliveTime 的时间单位。
- workQueue:任务队列,用于存放提交的任务,当提交的任务数大于 corePoolSize 时,会将任务放入任务队列中。
- HashMap 在 JDK1.8 之后的优化:
- 链表转红黑树:当链表长度大于 8 且数组长度大于 64 时,会将链表转换为红黑树,以提高查询效率。
- 扩容优化:扩容时会将链表节点按原顺序插入新数组,减少链表反转的开销。
- 哈希算法改进:采用了扰动函数来增加哈希值的随机性,减少哈希冲突。
- JVM 内存模型:
- 堆:存放对象实例,是垃圾回收的主要区域。
- 栈:存放局部变量、方法调用等信息。
- 方法区:存放类信息、常量、静态变量等。
- 程序计数器:记录当前线程执行的字节码指令地址。
- 本地方法栈:为本地方法提供服务。
- Spring 依赖注入方式:
- 构造器注入:通过构造函数传入依赖对象。
- setter 注入:通过 setter 方法传入依赖对象。
- 接口注入:通过实现特定接口来注入依赖对象。
- 基于注解的注入:如 @Autowired、@Resource 等注解。
- MyBatis 缓存机制:
- 一级缓存:是 SqlSession 级别的缓存,默认开启。在同一个 SqlSession 中,对相同 SQL 的查询会从缓存中获取数据,不会再次查询数据库。
- 二级缓存:是基于 namespace 级别的缓存,需要手动开启。多个 SqlSession 可以共享二级缓存,当一个 SqlSession 对数据进行修改时,会刷新二级缓存。
- Dubbo 集群容错策略:
- failover:失败自动切换,当调用失败时,会自动重试其他服务器。
- failfast:快速失败,当调用失败时,立即抛出异常,不再重试。
- failsafe:失败安全,当调用失败时,直接忽略,不抛出异常。
- failback:失败后自动恢复,当调用失败时,会在后台异步重试。
- forking:并行调用多个服务器,只要有一个成功就返回。
- broadcast:广播调用所有服务器,逐个调用,任意一个报错则报错。
- RabbitMq 消息确认机制:
- 发送端确认:
- 事务机制:通过 channel.txSelect()开启事务,channel.txCommit()提交事务,channel.txRollback()回滚事务。事务机制保证消息发送成功,但性能较低。
- Confirm 机制:通过 channel.confirmSelect()开启确认模式,发送消息后通过 channel.waitForConfirms()或 channel.waitForConfirmsOrDie()等待服务器确认。Confirm 机制有普通确认和批量确认两种方式。
- 接收端确认:
- 自动确认:消费者接收到消息后,自动确认消息已被接收。
- 手动确认:消费者接收到消息后,需要手动调用 channel.basicAck()方法来确认消息已被接收。
- 发送端确认:
- xxl-job 调度中心实现任务调度的原理:
- 调度中心存储任务信息:包括任务的执行表达式、任务参数等。
- 定时触发调度:根据任务的执行表达式,调度中心定时触发任务调度。
- 任务分发:调度中心将任务分发给执行器,执行器负责具体的任务执行。
- 任务执行监控:调度中心监控任务的执行情况,包括执行结果、执行时间等。
- 任务重试机制:当任务执行失败时,调度中心会根据配置进行重试。