《互联网大厂 Java 面试:核心知识、框架与中间件的全方位考察》

83 阅读10分钟

互联网大厂 Java 面试:核心知识、框架与中间件的全方位考察

王铁牛怀揣着忐忑与期待,走进了这家互联网大厂的面试会议室。严肃的面试官早已坐在那里,面前摆放着王铁牛的简历。面试,正式开始。

第一轮面试 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那说说 Java 中重载和重写的区别。 王铁牛:重载是在同一个类中,方法名相同但参数列表不同;重写是子类对父类方法的重新实现,方法名、参数列表和返回值类型都一样。 面试官:很好,看来基础很扎实。那 Java 中的多态是如何实现的? 王铁牛:多态主要通过继承、接口和方法重写来实现。父类引用指向子类对象,调用重写方法时会根据实际对象类型来执行。

第二轮面试 面试官:接下来聊聊 JUC 和多线程。什么是线程安全? 王铁牛:线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致等问题。 面试官:那 JUC 中的 CountDownLatch 是做什么用的? 王铁牛:呃……这个好像是用来协调多个线程执行顺序的,让一个或多个线程等待其他线程完成操作。 面试官:还算有点印象。那线程池的核心参数有哪些? 王铁牛:有核心线程数、最大线程数、线程空闲时间、时间单位、任务队列、线程工厂和拒绝策略。 面试官:回答得挺好。那说说线程池的工作流程。 王铁牛:嗯……就是先创建线程池,有任务来了就看核心线程有没有空闲,有就执行,没有就放队列里,队列满了就看最大线程数,还不行就执行拒绝策略。

第三轮面试 面试官:现在问问框架相关的。Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:那 Spring Boot 的自动配置原理是什么? 王铁牛:这个……好像是根据 classpath 里的依赖和配置文件,自动配置 Spring 应用。 面试官:那 MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译处理,能防止 SQL 注入;{} 是直接替换,有 SQL 注入风险。 面试官:看来对基础的还是了解的。那 Dubbo 是如何进行服务调用的? 王铁牛:呃……好像是通过注册中心找到服务提供者,然后进行远程调用。

面试结束 面试官合上手中的笔记本,表情严肃地说:“今天的面试就到这里,你先回去等通知吧。整体来看,你对一些基础的 Java 知识掌握得还可以,像基本数据类型、重载重写、线程池参数这些都回答得不错。但对于一些比较复杂的知识点,比如 JUC 中 CountDownLatch 的具体应用、Spring Boot 自动配置原理的详细过程、Dubbo 服务调用的具体流程等,回答得不够清晰和深入。我们后续会综合评估所有面试者的情况,再给你答复,你回去也可以再巩固一下相关知识。”

问题答案

  1. Java 中基本数据类型有哪些?
    • Java 有 8 种基本数据类型,可分为 4 类:
      • 整数类型:byte(1 字节,-128 到 127)、short(2 字节,-32768 到 32767)、int(4 字节,-2147483648 到 2147483647)、long(8 字节,范围更大)。
      • 浮点类型:float(4 字节,单精度浮点数)、double(8 字节,双精度浮点数)。
      • 字符类型:char(2 字节,用于表示单个字符,采用 Unicode 编码)。
      • 布尔类型:boolean(1 位,只有 true 和 false 两个值)。
  2. Java 中重载和重写的区别
    • 重载(Overloading):发生在同一个类中,方法名相同,但参数列表不同(参数的类型、个数或顺序不同)。重载与返回值类型、访问修饰符无关。例如:
public class OverloadExample {
    public int add(int a, int b) {
        return a + b;
    }
    public double add(double a, double b) {
        return a + b;
    }
}
- 重写(Overriding):发生在子类和父类之间,子类重写父类的方法。方法名、参数列表和返回值类型必须相同(Java 5 及以后,返回值类型可以是父类方法返回值类型的子类,称为协变返回类型)。访问修饰符不能比父类更严格,抛出的异常不能比父类方法抛出的异常更宽泛。例如:
class Parent {
    public void print() {
        System.out.println("Parent");
    }
}
class Child extends Parent {
    @Override
    public void print() {
        System.out.println("Child");
    }
}
  1. Java 中的多态是如何实现的
    • 多态的实现主要基于以下几个方面:
      • 继承:子类继承父类,拥有父类的属性和方法。
      • 方法重写:子类重写父类的方法,实现自己的逻辑。
      • 父类引用指向子类对象:可以通过父类的引用调用子类重写的方法,在运行时根据实际对象的类型来决定执行哪个方法。例如:
class Animal {
    public void sound() {
        System.out.println("Animal makes sound");
    }
}
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}
public class PolymorphismExample {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // 输出 "Dog barks"
    }
}
  1. 什么是线程安全
    • 在多线程环境下,如果多个线程同时访问共享资源,可能会出现数据不一致、脏读、幻读等问题。线程安全的代码能够保证在多线程环境下对共享资源的访问不会出现这些问题。例如,一个线程对共享变量进行写操作,另一个线程同时进行读操作,如果没有合适的同步机制,就可能读到不一致的数据。常见的实现线程安全的方式有使用 synchronized 关键字、Lock 接口、原子类等。
  2. JUC 中的 CountDownLatch 是做什么用的
    • CountDownLatch 是 JUC(Java Util Concurrency)包中的一个同步工具类,它允许一个或多个线程等待其他线程完成操作。CountDownLatch 内部维护一个计数器,在创建时指定初始值。线程可以调用 countDown() 方法将计数器减 1,当计数器变为 0 时,调用 await() 方法等待的线程将被唤醒继续执行。例如,在一个多线程任务中,主线程需要等待所有子线程完成任务后再继续执行:
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    // 模拟线程执行任务
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " completed");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        latch.await();
        System.out.println("All threads completed, main thread continues");
    }
}
  1. 线程池的核心参数有哪些
    • 线程池的核心参数包括:
      • corePoolSize(核心线程数):线程池创建后,默认情况下线程池中并没有任何线程,当有任务提交时,才会创建线程去执行任务。当线程池中的线程数量达到 corePoolSize 后,新的任务会被放入任务队列中。
      • maximumPoolSize(最大线程数):线程池允许创建的最大线程数。当任务队列满了,并且线程池中的线程数量小于 maximumPoolSize 时,会创建新的线程来执行任务。
      • keepAliveTime(线程空闲时间):当线程池中的线程数量超过 corePoolSize 时,多余的线程在空闲 keepAliveTime 时间后会被销毁。
      • unit(时间单位):keepAliveTime 的时间单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
      • workQueue(任务队列):用于存储等待执行的任务的队列。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
      • threadFactory(线程工厂):用于创建线程的工厂,通过线程工厂可以自定义线程的名称、优先级等属性。
      • handler(拒绝策略):当任务队列满了,并且线程池中的线程数量达到 maximumPoolSize 时,新提交的任务会被拒绝,此时会调用拒绝策略来处理这些任务。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程处理任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  2. 线程池的工作流程
    • 当有新的任务提交到线程池时,线程池的工作流程如下:
      • 步骤 1:判断线程池中的线程数量是否小于 corePoolSize,如果是,则创建新的线程来执行任务。
      • 步骤 2:如果线程池中的线程数量已经达到 corePoolSize,则将任务放入任务队列中。
      • 步骤 3:如果任务队列已满,且线程池中的线程数量小于 maximumPoolSize,则创建新的线程来执行任务。
      • 步骤 4:如果任务队列已满,且线程池中的线程数量已经达到 maximumPoolSize,则根据拒绝策略来处理新提交的任务。
  3. Spring 的核心特性有哪些
    • Spring 的核心特性主要有:
      • IoC(控制反转):也称为依赖注入(DI),是一种设计模式,将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。通过 Spring 容器来创建和管理对象,对象之间的依赖关系由容器注入,降低了代码的耦合度。例如,在 Spring 中可以通过注解(如 @Autowired)或 XML 配置来实现依赖注入。
      • AOP(面向切面编程):允许开发者在不修改原有代码的情况下,对程序进行增强。通过将横切关注点(如日志记录、事务管理等)与业务逻辑分离,提高了代码的可维护性和复用性。在 Spring 中,可以使用 AspectJ 或 Spring AOP 来实现 AOP。
  4. Spring Boot 的自动配置原理是什么
    • Spring Boot 的自动配置原理基于以下几个关键部分:
      • Spring Boot Starter:它是一组依赖的集合,包含了开发特定功能所需的所有依赖。例如,spring-boot-starter-web 包含了开发 Web 应用所需的所有依赖。
      • @EnableAutoConfiguration 注解:该注解开启了 Spring Boot 的自动配置功能。它会根据 classpath 里的依赖和配置文件,自动配置 Spring 应用。
      • SpringFactoriesLoader:Spring Boot 在启动时会使用 SpringFactoriesLoader 从 META-INF/spring.factories 文件中加载自动配置类。这些自动配置类会根据条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等)来判断是否需要进行自动配置。
      • 条件注解:Spring Boot 提供了一系列的条件注解,用于根据不同的条件来决定是否加载某个自动配置类。例如,@ConditionalOnClass 表示只有当指定的类在 classpath 中存在时,才会加载该自动配置类;@ConditionalOnMissingBean 表示只有当容器中不存在指定类型的 Bean 时,才会加载该自动配置类。
  5. MyBatis 中 #{} 和 ${} 的区别是什么
    • #{} 是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行预编译,防止 SQL 注入。例如:
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
- ${} 是直接替换,MyBatis 在处理 ${} 时,会将 ${} 直接替换为传入的值。这种方式存在 SQL 注入的风险,因为如果传入的值包含恶意的 SQL 语句,可能会导致数据库被攻击。例如:
<select id="getUserByColumnName" parameterType="String" resultType="User">
    SELECT * FROM users WHERE ${columnName} = 'value'
</select>
  1. Dubbo 是如何进行服务调用的
    • Dubbo 的服务调用主要分为以下几个步骤:
      • 服务注册:服务提供者启动时,会将自己提供的服务信息(如服务接口、服务地址等)注册到注册中心(如 ZooKeeper、Nacos 等)。
      • 服务发现:服务消费者启动时,会从注册中心获取服务提供者的信息,并缓存到本地。
      • 远程调用:服务消费者通过代理对象调用服务接口的方法,代理对象会根据本地缓存的服务提供者信息,选择一个合适的服务提供者进行远程调用。Dubbo 支持多种远程调用协议,如 Dubbo 协议、HTTP 协议等。
      • 调用过程:在远程调用过程中,Dubbo 会对请求进行序列化,将请求数据转换为字节流,通过网络传输到服务提供者。服务提供者接收到请求后,会对请求进行反序列化,执行相应的服务方法,并将结果返回给服务消费者。服务消费者接收到结果后,会对结果进行反序列化,得到最终的调用结果。