《互联网大厂 Java 面试:核心知识、框架与中间件大考验》

38 阅读9分钟

互联网大厂 Java 面试:核心知识、框架与中间件大考验

在互联网大厂宽敞明亮的面试室内,气氛略显紧张。严肃的面试官坐在桌前,对面坐着前来应聘 Java 开发岗位的王铁牛。

第一轮面试开始

  • 面试官:首先问你几个 Java 核心知识的问题。Java 中基本数据类型有哪些?
  • 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。
  • 面试官:回答正确。那 Java 中如何实现多态呢?
  • 王铁牛:通过继承父类或者实现接口,然后进行方法重写,再使用父类引用指向子类对象就能实现多态。
  • 面试官:不错。那 String、StringBuilder 和 StringBuffer 有什么区别?
  • 王铁牛:String 是不可变的,每次对 String 进行操作都会生成一个新的 String 对象。StringBuilder 是非线程安全的,而 StringBuffer 是线程安全的,它们都是可变的。
  • 面试官:回答得很好,基础知识很扎实。

第二轮面试开始

  • 面试官:接下来聊聊 JUC 和多线程相关的。什么是线程池?为什么要使用线程池?
  • 王铁牛:线程池就是管理线程的一个池子嘛,使用线程池可以减少创建和销毁线程的开销,提高性能。
  • 面试官:那线程池有哪些常用的创建方式?
  • 王铁牛:这个……好像有 Executors 工具类可以创建,有固定大小线程池、单线程线程池什么的。
  • 面试官:那在高并发场景下,使用 Executors 创建线程池可能会有什么问题?
  • 王铁牛:嗯……可能会出现资源耗尽的问题吧,具体我也不太清楚。
  • 面试官:这里你回答得不够清晰,在高并发场景下,使用 Executors 创建的线程池可能会因为队列长度或最大线程数设置不合理,导致 OOM 等问题。

第三轮面试开始

  • 面试官:现在来谈谈框架相关的。Spring 的核心特性有哪些?
  • 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。
  • 面试官:那 Spring Boot 相对于 Spring 有什么优势?
  • 王铁牛:Spring Boot 简化了 Spring 的配置,有自动配置功能,能快速搭建项目。
  • 面试官:MyBatis 中 #{} 和 ${} 的区别是什么?
  • 王铁牛:这个……我好像记得 #{} 是预编译的,能防止 SQL 注入,${} 好像是直接替换,但是具体细节我有点忘了。
  • 面试官:还有像 Dubbo、RabbitMq、xxl - job、Redis 这些中间件,你简单说下 Dubbo 的工作原理吧。
  • 王铁牛:Dubbo 就是个分布式服务框架,原理嘛……我就不太能说清楚了。

面试接近尾声,面试官扶了扶眼镜,说道:“今天的面试就到这里了,你的基础知识有一定的掌握,对于一些简单问题回答得还不错,但对于一些复杂的业务场景和原理性的问题,回答得不够清晰和深入。你先回家等通知吧,后续我们会综合评估后给你反馈。”

问题答案

  1. Java 中基本数据类型有哪些?
    • Java 中有 8 种基本数据类型,分为 4 类:
      • 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
      • 浮点类型:float(4 字节)、double(8 字节)。
      • 字符类型:char(2 字节)。
      • 布尔类型:boolean(理论上 1 位,但在 Java 中没有明确规定大小)。
  2. Java 中如何实现多态呢?
    • 多态是指同一个行为具有多个不同表现形式或形态的能力。Java 实现多态有三个必要条件:
      • 继承:存在子类继承父类或者实现类实现接口的关系。
      • 重写:子类或实现类重写父类或接口的方法。
      • 父类引用指向子类对象:使用父类类型的变量来引用子类的对象,通过该引用调用重写的方法时,会根据实际引用的对象类型来决定调用哪个类的方法。 示例代码:
class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // 输出 "Dog barks"
    }
}
  1. String、StringBuilder 和 StringBuffer 有什么区别?
    • String:是不可变的,一旦创建,其值不能被修改。每次对 String 进行操作(如拼接、替换等),都会创建一个新的 String 对象,这会导致频繁的内存分配和垃圾回收,影响性能。
    • StringBuilder:是可变的,非线程安全的。它提供了一系列的方法用于修改字符串内容,如 append()、insert() 等。由于是非线程安全的,在单线程环境下使用 StringBuilder 性能较好。
    • StringBuffer:也是可变的,但它是线程安全的。在多线程环境下,如果多个线程同时操作同一个 StringBuffer 对象,它能保证数据的一致性。不过,由于要进行同步操作,其性能相对 StringBuilder 较低。
  2. 什么是线程池?为什么要使用线程池?
    • 线程池:是一种线程使用模式,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完毕后线程不会销毁,而是返回到线程池中等待下一个任务。
    • 使用线程池的原因
      • 减少线程创建和销毁的开销:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,提高系统性能。
      • 提高响应速度:当有任务提交时,不需要等待线程创建,直接从线程池中获取线程执行任务,减少了任务的等待时间。
      • 便于线程管理:可以控制线程池中的线程数量,避免创建过多线程导致系统资源耗尽。
  3. 线程池有哪些常用的创建方式?
    • Java 中可以使用 Executors 工具类来创建不同类型的线程池:
      • newFixedThreadPool(int nThreads):创建一个固定大小的线程池,线程池中的线程数量始终保持为 nThreads。
      • newSingleThreadExecutor():创建一个单线程的线程池,只有一个线程来执行任务,保证任务按顺序执行。
      • newCachedThreadPool():创建一个可缓存的线程池,线程池中的线程数量会根据任务的数量动态调整。如果线程池中有空闲线程,会优先使用空闲线程;如果没有空闲线程,则会创建新的线程。
      • newScheduledThreadPool(int corePoolSize):创建一个可以定时执行任务的线程池,核心线程数为 corePoolSize。 示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        // 创建单线程线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        // 创建可缓存的线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    }
}
  1. 在高并发场景下,使用 Executors 创建线程池可能会有什么问题?
    • newFixedThreadPool 和 newSingleThreadExecutor:使用的是 LinkedBlockingQueue,其默认大小为 Integer.MAX_VALUE,在高并发场景下,可能会有大量任务堆积在队列中,导致内存占用过高,甚至出现 OOM(OutOfMemoryError)。
    • newCachedThreadPool:允许创建的线程数量为 Integer.MAX_VALUE,在高并发场景下,可能会创建大量的线程,导致系统资源耗尽,也会出现 OOM。 因此,在高并发场景下,建议使用 ThreadPoolExecutor 来手动创建线程池,根据实际需求合理设置线程池的参数。
  2. Spring 的核心特性有哪些?
    • IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。Spring 容器负责创建对象,并将对象之间的依赖关系注入到对象中,从而降低了代码的耦合度。
    • AOP(面向切面编程):是一种编程范式,它允许在不修改原有代码的情况下,对程序的某些功能进行增强。AOP 通过将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高了代码的可维护性和可复用性。
  3. Spring Boot 相对于 Spring 有什么优势?
    • 简化配置:Spring Boot 提供了自动配置功能,根据项目中引入的依赖,自动配置 Spring 应用的各种组件,减少了大量的 XML 配置或 Java 配置代码。
    • 快速搭建项目:Spring Boot 提供了 Spring Initializr 工具,可以快速生成项目骨架,开发者只需要添加必要的依赖和编写业务代码即可。
    • 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,不需要额外配置和部署服务器,直接运行应用程序即可。
    • 生产级特性:Spring Boot 提供了一系列生产级特性,如监控、健康检查、配置管理等,方便开发者在生产环境中对应用程序进行管理和维护。
  4. MyBatis 中 #{} 和 ${} 的区别是什么?
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入攻击,因为参数会进行预编译处理,不会直接拼接到 SQL 语句中。
    • **:是直接替换,MyBatis在处理{}**:是直接替换,MyBatis 在处理 {} 时,会将 直接替换为参数的值。这种方式可能会导致SQL注入攻击,因为参数是直接拼接到SQL语句中的。一般情况下,{} 直接替换为参数的值。这种方式可能会导致 SQL 注入攻击,因为参数是直接拼接到 SQL 语句中的。一般情况下,{} 用于动态传入表名、列名等。 示例代码:
<!-- 使用 #{} -->
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>

<!-- 使用 ${} -->
<select id="getUserByColumnName" parameterType="map" resultType="User">
    SELECT ${columnName} FROM users WHERE id = #{id}
</select>
  1. Dubbo 的工作原理
    • Dubbo 是一个分布式服务框架,其工作原理主要包括以下几个步骤:
      • 服务注册:服务提供者在启动时,将自己提供的服务信息(如服务接口、服务地址等)注册到注册中心(如 Zookeeper、Nacos 等)。
      • 服务订阅:服务消费者在启动时,向注册中心订阅自己需要的服务,注册中心会将服务提供者的信息推送给服务消费者。
      • 远程调用:服务消费者根据获取到的服务提供者信息,使用远程调用协议(如 Dubbo 协议、HTTP 协议等)调用服务提供者的服务。
      • 服务监控:Dubbo 提供了服务监控功能,监控服务的调用次数、调用时间等信息,方便开发者进行性能调优和故障排查。
      • 服务治理:Dubbo 支持服务路由、负载均衡、集群容错等服务治理功能,保证服务的高可用性和性能。

整个调用过程中,注册中心负责服务的注册和发现,服务提供者提供具体的服务实现,服务消费者调用服务,通过远程调用协议进行通信,Dubbo 框架在中间进行协调和管理,确保服务的高效、稳定运行。