面试官:第一轮提问开始。首先,说说 Java 中的多线程创建方式有哪些?
王铁牛:可以通过继承 Thread 类,重写 run 方法来创建;也可以实现 Runnable 接口,重写 run 方法,然后把实现类对象作为参数传递给 Thread 类的构造方法来创建。
面试官:回答得不错。那线程池的核心参数有哪些?
王铁牛:核心参数有 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程存活时间)、unit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)、handler(拒绝策略)。
面试官:很好。再问一个,HashMap 在 JDK1.8 中做了哪些优化?
王铁牛:在 JDK1.8 中,HashMap 底层结构引入了红黑树,当链表长度大于 8 且数组长度大于 64 时,链表会转换为红黑树,提高查询效率。
面试官:第一轮提问结束。下面进行第二轮提问。讲讲 JVM 的内存区域有哪些?
王铁牛:有程序计数器、虚拟机栈、本地方法栈、堆、方法区。
面试官:那 JVM 的垃圾回收算法有哪些?
王铁牛:有标记清除算法、标记整理算法、复制算法、分代收集算法。
面试官:Spring 框架的核心特性有哪些?
王铁牛:Spring 框架的核心特性有依赖注入、面向切面编程、IoC 容器等。
面试官:第二轮提问完毕。进入第三轮提问。说说 MyBatis 的工作原理。
王铁牛:MyBatis 首先读取配置文件,解析 SQL 语句,然后通过反射机制创建 SQL 执行对象,执行 SQL 并返回结果。
面试官:Dubbo 的集群容错模式有哪些?
王铁牛:有 failover(失败自动切换)、failfast(快速失败)、failsafe(失败安全)、failback(失败自动恢复)、forking(并行调用多个服务)等。
面试官:RabbitMq 的消息确认机制是怎样的?
王铁牛:不太清楚……
面试官:好,三轮提问结束。回家等通知吧。
答案:
- Java 多线程创建方式:
- 继承 Thread 类:通过继承 Thread 类,并重写其 run 方法来定义线程执行的任务。例如:
class MyThread extends Thread { @Override public void run() { System.out.println("This is a thread created by extending Thread class."); } } MyThread thread = new MyThread(); thread.start();- 实现 Runnable 接口:实现 Runnable 接口的 run 方法,然后将实现类实例作为参数传递给 Thread 类的构造方法来创建线程。比如:
class MyRunnable implements Runnable { @Override public void run() { System.out.println("This is a thread created by implementing Runnable interface."); } } Thread thread = new Thread(new MyRunnable()); thread.start(); - 线程池核心参数:
- corePoolSize(核心线程数):线程池创建时初始化的线程数,当提交的任务数小于 corePoolSize 时,会创建新线程来执行任务。
- maximumPoolSize(最大线程数):线程池能够容纳的最大线程数,当任务数超过 corePoolSize 且任务队列已满时,会创建新线程直到线程数达到 maximumPoolSize。
- keepAliveTime(线程存活时间):当线程数大于 corePoolSize 时,多余的线程在空闲一段时间后会被销毁,这段空闲时间就是 keepAliveTime。
- unit(时间单位):keepAliveTime 的时间单位,如 TimeUnit.SECONDS(秒)。
- workQueue(任务队列):用于存放提交到线程池但尚未执行的任务,常见的有 ArrayBlockingQueue、LinkedBlockingQueue 等。
- threadFactory(线程工厂):用于创建线程的工厂,可以自定义线程的名称、优先级等属性。
- handler(拒绝策略):当线程池达到最大线程数且任务队列已满时,会调用拒绝策略来处理新提交的任务,常见的拒绝策略有 AbortPolicy(抛出异常)、CallerRunsPolicy(调用者运行任务)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
- HashMap 在 JDK1.8 中的优化:
- 引入红黑树:在 JDK1.8 中,当链表长度大于 8 且数组长度大于 64 时,链表会转换为红黑树。红黑树是一种自平衡二叉查找树,相比于链表,其查询效率更高。例如,在大量元素存储的情况下,链表的查找时间复杂度是 O(n),而红黑树的查找时间复杂度是 O(log n)。
- JVM 内存区域:
- 程序计数器:记录当前线程执行的字节码指令地址,是线程私有的内存区域。
- 虚拟机栈:每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,也是线程私有的。
- 本地方法栈:与虚拟机栈类似,用于执行本地方法,同样是线程私有的。
- 堆:是 JVM 中最大的内存区域,用于存储对象实例,是线程共享的。
- 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,也是线程共享的。在 JDK1.8 中,方法区被元空间(MetaSpace)取代,元空间使用本地内存,而不再像之前方法区那样受限于 JVM 堆内存大小。
- JVM 垃圾回收算法:
- 标记清除算法:分为两个阶段,首先标记出所有需要回收的对象,然后统一回收这些对象所占用的空间。但这种算法会产生内存碎片。
- 标记整理算法:标记阶段和标记清除算法一样,在清除阶段,不是简单地回收可回收对象,而是将存活对象向一端移动,然后直接清理掉端边界以外的内存。
- 复制算法:将内存空间分为两块相等的区域,每次只使用其中一块,当这一块内存使用完后,将存活对象复制到另一块区域,然后清除使用过的区域。这种算法适用于对象存活率较低的情况。
- 分代收集算法:根据对象的存活周期将内存划分为不同的区域,一般分为新生代、老年代和永久代(JDK1.8 后为元空间)。针对不同区域采用不同的垃圾回收算法,如新生代采用复制算法,老年代采用标记清除或标记整理算法等。
- Spring 框架的核心特性:
- 依赖注入(Dependency Injection):通过控制反转(IoC)实现,将对象的依赖关系由程序主动创建改为由容器注入,降低了对象之间的耦合度。例如,一个类需要依赖另一个类来完成某项功能,传统方式是在该类内部直接创建依赖类的实例,而使用 Spring 可以通过配置文件或注解让 Spring 容器将依赖类的实例注入到该类中。
- 面向切面编程(Aspect Oriented Programming,AOP):允许将一些通用功能(如日志记录、事务管理等)从业务逻辑中分离出来,以切面的形式织入到业务逻辑中,提高代码的可维护性和复用性。
- IoC 容器:负责创建、配置和管理对象,它是 Spring 框架的核心,通过读取配置文件或注解来实例化对象,并根据依赖关系进行注入和装配。
- MyBatis 的工作原理:
- 读取配置文件:MyBatis 首先读取配置文件(如 mybatis-config.xml),解析其中的配置信息,包括数据库连接信息、映射文件路径等。
- 解析 SQL 语句:读取映射文件(如 UserMapper.xml),解析其中的 SQL 语句,将其封装成 MappedStatement 对象。
- 创建 SQL 执行对象:通过反射机制创建 SQL 执行对象,例如 PreparedStatement 对象,用于执行 SQL 语句。
- 执行 SQL 并返回结果:将参数设置到 SQL 执行对象中,执行 SQL 语句,然后根据结果映射规则将结果封装成 Java 对象返回给调用者。例如,通过 ResultMap 配置将查询结果的每一列映射到 Java 对象的相应属性上。
- Dubbo 的集群容错模式:
- failover(失败自动切换):当调用失败时,会自动切换到其他可用的服务提供者进行重试,默认重试次数是 2 次。适用于读操作等对一致性要求不高的场景。
- failfast(快速失败):调用失败时,直接抛出异常,不再进行重试。适用于幂等操作,即多次调用结果相同的操作。
- failsafe(失败安全):调用失败时,直接忽略,不抛出异常,也不进行重试。适用于写操作等对结果准确性要求不高的场景。
- failback(失败自动恢复):调用失败时,会将失败的请求记录下来,然后定时重试。适用于消息通知等场景。
- forking(并行调用多个服务):并行调用多个服务提供者,只要有一个成功就返回结果。适用于对响应时间要求较高的场景。
- RabbitMq 的消息确认机制:
- 生产者确认机制:
- 事务机制:生产者发送消息前开启事务(channel.txSelect()),然后发送消息,如果消息发送成功,提交事务(channel.txCommit()),如果失败,回滚事务(channel.txRollback())。但这种方式性能较低。
- Confirm 机制:生产者将信道设置成 confirm 模式(channel.confirmSelect()),然后发送消息。如果消息成功到达 Broker,Broker 会返回一个 Confirm 通知;如果消息发送失败,Broker 会返回一个 Nack 通知。生产者可以根据这些通知来处理消息发送结果。
- 消费者确认机制:
- 自动确认:消费者设置 autoAck = true,当消费者接收到消息后,会自动确认消息已被接收,Broker 会立即将消息从队列中删除。
- 手动确认:消费者设置 autoAck = false,接收到消息后,不会自动确认,需要调用 channel.basicAck 方法手动确认消息。可以选择一次性确认多条消息,也可以逐一确认。如果在未确认消息前消费者断开连接,Broker 会将这些消息重新分发给其他消费者。
- 生产者确认机制: