面试官:请简要介绍一下 Java 中的多线程机制,比如线程的创建方式有哪些?
王铁牛:线程创建方式有继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。
面试官:嗯,回答得不错。那说说线程池的作用和优势吧。
王铁牛:线程池能复用线程,减少线程创建和销毁开销,提高响应速度,还能控制并发数量。
面试官:很好。接下来问几个关于 JVM 的问题,类加载机制分哪几个阶段?
王铁牛:类加载机制分加载、验证、准备、解析、初始化阶段。
面试官:第一轮面试结束,整体表现还行。下面进入第二轮。说说 HashMap 的底层数据结构以及扩容机制。
王铁牛:HashMap 底层是数组+链表+红黑树。扩容就是当元素个数超过阈值且链表长度大于 8 时,链表会转换为红黑树,当元素个数小于 6 时,红黑树又会转换为链表,然后数组扩容。
面试官:ArrayList 的优缺点是什么?
王铁牛:优点是随机访问速度快,缺点是插入和删除效率低,因为涉及元素移动。
面试官:Spring 框架中依赖注入的方式有哪些?
王铁牛:有构造器注入、setter 方法注入、基于注解的注入。
面试官:第二轮结束,表现一般。现在进入第三轮。Dubbo 的集群容错模式有哪些?
王铁牛:有 failover、failfast、failsafe、failback、forking 等。
面试官:RabbitMq 的消息确认机制是怎样的?
王铁牛:有发送确认和接收确认,发送确认又分事务确认和 publisher confirm 模式,接收确认就是消费者接收到消息后给生产者反馈。
面试官:xxl-job 是如何实现分布式任务调度的?
王铁牛:(胡乱回答一通,讲得很不清晰)
面试官:好的,面试就到这里,回去等通知吧。
答案:
- 线程创建方式:
- 继承 Thread 类:通过继承 Thread 类并重写 run 方法来创建线程。这种方式的优点是简单直接,缺点是 Java 是单继承,继承了 Thread 类后就不能再继承其他类了。
- 实现 Runnable 接口:实现 Runnable 接口的 run 方法。这种方式避免了单继承的局限性,更适合多个线程共享一个资源的场景。
- 实现 Callable 接口:实现 Callable 接口的 call 方法,与 Runnable 不同的是 call 方法有返回值,并且可以抛出异常。
- 线程池的作用和优势:
- 作用:复用线程,减少线程创建和销毁的开销,提高系统性能和响应速度。
- 优势:
- 提高响应速度:避免了频繁创建和销毁线程的时间开销。
- 控制资源消耗:可以限制线程的数量,避免资源耗尽。
- 便于管理:统一管理线程的创建、销毁和复用。
- JVM 类加载机制阶段:
- 加载:通过类的全限定名获取二进制字节流,将其加载到内存中,并创建一个 java.lang.Class 对象。
- 验证:验证字节流的正确性,确保其符合 JVM 的规范。
- 准备:为类的静态变量分配内存,并设置默认初始值。
- 解析:将符号引用替换为直接引用。
- 初始化:执行类的静态代码块和静态变量的赋值操作。
- HashMap 底层数据结构及扩容机制:
- 底层数据结构:由数组、链表和红黑树组成。当链表长度大于 8 且数组容量大于 64 时,链表会转换为红黑树,以提高查询效率。
- 扩容机制:当元素个数超过阈值(容量*负载因子)时,数组会进行扩容。扩容时,会创建一个新的更大的数组,然后将原数组中的元素重新计算位置后放入新数组中。
- ArrayList 优缺点:
- 优点:
- 随机访问速度快:基于数组实现,通过下标可以直接定位到元素。
- 实现简单:代码结构相对简单。
- 缺点:
- 插入和删除效率低:插入和删除元素时需要移动大量元素。
- 占用连续内存空间:可能导致内存碎片。
- 优点:
- Spring 依赖注入方式:
- 构造器注入:通过构造函数传入依赖对象,优点是注入的依赖对象在构造函数执行时就已经可用,缺点是当依赖对象较多时,构造函数参数列表会很长。
- setter 方法注入:通过 setter 方法设置依赖对象,优点是可以灵活地更换依赖对象,缺点是可能会出现 NPE(空指针异常),因为在调用 setter 方法前依赖对象可能未被初始化。
- 基于注解的注入:常用的注解有 @Autowired、@Resource 等,@Autowired 按照类型进行注入,@Resource 可以按照名称或类型进行注入,使用注解可以使代码更加简洁。
- Dubbo 集群容错模式:
- failover:失败自动切换,当调用失败时,会自动重试其他服务器。
- failfast:快速失败,当调用失败时,立即抛出异常,不再重试。
- failsafe:失败安全,当调用失败时,直接忽略,不抛出异常。
- failback:失败后自动恢复,当调用失败时,会在后台异步重试。
- forking:并行调用多个服务器,只要有一个成功就返回。
- RabbitMq 消息确认机制:
- 发送确认:
- 事务确认:通过 channel.txSelect()开启事务,在发送消息后调用 channel.txCommit()提交事务,如果出现异常则调用 channel.txRollback()回滚事务。
- publisher confirm:通过 channel.confirmSelect()开启确认模式,发送消息后通过 addConfirmListener 监听确认结果。
- 接收确认:消费者接收到消息后,通过 channel.basicAck 方法向 RabbitMq 发送确认消息,表示已成功接收。
- 发送确认:
- xxl-job 实现分布式任务调度:
- 调度中心:负责管理任务调度规则、任务执行器信息等。
- 执行器:负责实际执行任务。
- 注册中心:用于调度中心和执行器之间的服务发现和注册。
- 调度流程:调度中心根据任务调度规则,从注册中心获取可用的执行器,然后将任务分配给执行器执行。执行器执行完任务后,将执行结果反馈给调度中心。