《互联网大厂Java求职者面试:核心知识大考验》

96 阅读8分钟

面试官:第一轮提问开始,首先问你,Java 中多线程的实现方式有哪些?

王铁牛:嗯……有继承 Thread 类和实现 Runnable 接口这两种方式。

面试官:回答得不错。那再问你,线程池的核心参数有哪些?

王铁牛:这个我知道,有 corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory 和 handler。

面试官:很好。最后一个问题,简述一下 HashMap 的工作原理。

王铁牛:呃……就是通过 key 的哈希值找到对应的桶位置,然后存储或者查找 value。

面试官:好,第一轮提问结束。

面试官:第二轮提问,JVM 的内存区域分为哪几块?

王铁牛:有程序计数器、虚拟机栈、本地方法栈、堆和方法区。

面试官:那类加载机制的过程是什么样的?

王铁牛:这个……是加载、验证、准备、解析、初始化。

面试官:Spring 框架的核心特性有哪些?

王铁牛:我想想……有依赖注入、面向切面编程、IoC 容器。

面试官:第二轮提问完毕。

面试官:第三轮提问,Dubbo 的集群容错模式有哪些?

王铁牛:嗯……好像有 failover、failfast、failsafe 这些。

面试官:RabbitMq 的消息确认机制是怎样的?

王铁牛:不太清楚,瞎猜一个,就是发送方和接收方确认收到消息?

面试官:xxl-job 的执行器类型有哪些?

王铁牛:这个真不知道。

面试官:好,面试结束,回去等通知吧。

答案

  1. Java 中多线程的实现方式
    • 继承 Thread 类:定义一个类继承 Thread 类,重写 run 方法,在 run 方法中编写线程执行的代码。启动线程时,创建该类的实例并调用 start 方法。这种方式的缺点是 Java 是单继承,一个类继承了 Thread 类就不能再继承其他类了。
    • 实现 Runnable 接口:定义一个类实现 Runnable 接口,实现其中的 run 方法。然后创建 Thread 类的实例,将实现了 Runnable 接口的类的实例作为参数传递给 Thread 的构造函数,最后调用 Thread 的 start 方法启动线程。这种方式避免了单继承的局限性,更灵活。
  2. 线程池的核心参数
    • corePoolSize:线程池的核心线程数,当提交的任务数小于 corePoolSize 时,线程池会创建新的线程来执行任务。
    • maximumPoolSize:线程池允许的最大线程数,当提交的任务数大于 corePoolSize 且任务队列已满时,会创建新的线程,直到线程数达到 maximumPoolSize。
    • keepAliveTime:线程池中的线程在空闲时的存活时间,当线程空闲时间超过 keepAliveTime 时,非核心线程会被销毁。
    • unit:keepAliveTime 的时间单位。
    • workQueue:任务队列,用于存储提交的任务,当提交的任务数大于 corePoolSize 时,任务会被放入 workQueue 中。
    • threadFactory:线程工厂,用于创建线程,可自定义线程的名称、优先级等属性。
    • handler:任务拒绝策略,当线程池中的线程数达到 maximumPoolSize 且任务队列已满时,会调用 handler 来处理新提交的任务。常见的任务拒绝策略有 AbortPolicy(抛出异常)、CallerRunsPolicy(调用者运行)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃最旧的任务)。
  3. HashMap 的工作原理
    • 哈希值计算:首先计算 key 的哈希值,通过 key 的 hashCode 方法得到一个 int 类型的哈希值。
    • 桶位置确定:然后通过哈希值和数组长度进行位运算,得到桶的位置。例如,假设数组长度为 16,哈希值为 10,则桶位置为 10 & (16 - 1) = 2。
    • 存储元素:如果桶为空,则直接将新元素存储到该桶中。如果桶不为空,则需要处理哈希冲突。HashMap 采用链地址法处理哈希冲突,即每个桶是一个链表,新元素会被添加到链表的头部。
    • 扩容机制:当 HashMap 中的元素个数超过数组长度 * 负载因子(默认 0.75)时,会进行扩容。扩容时会创建一个新的更大的数组,将原数组中的元素重新计算哈希值并放入新数组中。
  4. JVM 的内存区域
    • 程序计数器:当前线程所执行的字节码的行号指示器,每个线程都有一个独立的程序计数器。
    • 虚拟机栈:每个方法执行时都会创建一个栈帧,栈帧中包含局部变量表、操作数栈、动态链接、方法出口等信息。虚拟机栈用于存储栈帧,是线程私有的内存区域。
    • 本地方法栈:与虚拟机栈类似,用于执行本地方法(用 C、C++ 等编写的方法)。
    • :是 JVM 中最大的内存区域,所有对象实例都在堆中分配内存。堆是线程共享的内存区域。
    • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是线程共享的内存区域。
  5. 类加载机制的过程
    • 加载:通过类的全限定名获取定义此类的二进制字节流,将字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。
    • 验证:确保加载的类的正确性,包括文件格式验证、元数据验证、字节码验证、符号引用验证等。
    • 准备:为类的静态变量分配内存,并设置默认初始值。例如,int 类型的静态变量默认初始值为 0。
    • 解析:将常量池中的符号引用替换为直接引用。符号引用是一组符号来描述所引用的目标,直接引用是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。
    • 初始化:执行类构造器 方法的过程。 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。在执行 方法时,会按照语句在源文件中的顺序依次执行。
  6. Spring 框架的核心特性
    • 依赖注入(Dependency Injection):通过控制反转(IoC)容器,将对象之间的依赖关系由程序代码直接控制改为由容器来管理。例如,可以通过 XML 配置文件或注解的方式,将一个对象的依赖对象注入到该对象中。
    • 面向切面编程(Aspect - Oriented Programming,AOP):将业务逻辑的各个部分进行分离,提取出一些公共的功能,如日志记录、事务管理等,将这些公共功能作为切面,通过动态代理等方式织入到业务逻辑中。这样可以减少代码的重复,提高代码的可维护性。
    • IoC 容器:负责创建、配置和管理对象之间的依赖关系。IoC 容器通过读取配置文件或注解信息,实例化对象并注入依赖,使得对象之间的依赖关系更加清晰和易于管理。
  7. Dubbo 的集群容错模式
    • failover:失败自动切换,当出现调用失败时,会自动重试其他服务器,默认重试次数为 2 次。
    • failfast:快速失败,当调用失败时,立即抛出异常,不进行重试。
    • failsafe:失败安全,当调用失败时,直接忽略,不抛出异常,常用于写操作,如记录日志等。
    • failback:失败自动恢复,当调用失败时,会在后台异步重试,通常用于消息通知等场景。
    • forking:并行调用多个服务器,只要有一个成功就返回,用于实时性要求较高的场景,但会消耗更多资源。
    • broadcast:广播调用所有提供者,逐个调用,任意一个报错则报错,通常用于通知所有提供者更新缓存等场景。
  8. RabbitMq 的消息确认机制
    • 生产者确认
      • 事务机制:生产者发送消息前通过 channel.txSelect 开启事务,发送消息后通过 channel.txCommit 提交事务,如果发送过程中出现异常,可通过 channel.txRollback 回滚事务。这种方式确保消息发送成功,但性能较低。
      • confirm 机制:生产者通过 channel.confirmSelect 开启 confirm 模式,发送消息后通过 addConfirmListener 监听 confirm 事件。confirm 事件有两种,一种是消息发送到交换器后触发的 ConfirmCallback,另一种是消息从交换器路由到队列后触发的 ReturnCallback。生产者可根据不同的 confirm 事件进行相应处理,如记录日志、重发消息等。
    • 消费者确认
      • 自动确认:消费者在接收到消息后,自动确认消息已被接收。这种方式简单,但可能会导致消息丢失,如果消费者在处理消息时出现异常,未处理的消息会被自动确认。
      • 手动确认:消费者通过 channel.basicAck 方法手动确认消息。可以逐个确认消息,也可以批量确认消息。手动确认可以更好地控制消息的处理过程,确保消息被正确处理后才进行确认。
  9. xxl - job 的执行器类型
    • BEAN 模式:执行器为 Spring Bean 实例,通过 Spring 容器管理,适用于集成 Spring 框架的项目。
    • GLUE 模式:支持在线编辑任务代码,支持多种语言,如 Java、Python、Shell 等。代码在执行时会被动态编译或解释执行。
    • CMD 模式:通过执行 shell 脚本或命令来实现任务,适用于需要执行外部命令的场景。
    • HTTP 模式:通过 HTTP 请求触发任务执行,适用于与其他系统进行交互的场景。
    • SDK 模式:提供了 Java SDK,方便在项目中集成 xxl - job 任务,可通过代码直接调用任务执行。