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

38 阅读9分钟

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

在互联网大厂的一间明亮面试室内,严肃的面试官坐在桌前,对面是略显紧张的求职者王铁牛。面试开始了,一场对Java知识深度与广度的考验拉开帷幕。

第一轮面试 面试官:首先,我们从基础的Java核心知识开始。你能说一下Java中的基本数据类型有哪些吗? 王铁牛:这个我知道,Java的基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那在 Java 里,String 是基本数据类型吗,为什么? 王铁牛:String 不是基本数据类型,它是一个类。因为基本数据类型是 Java 语言预先定义好的简单数据类型,而 String 类提供了很多方法来操作字符串,有自己的属性和行为。 面试官:很好。那说说 String、StringBuilder 和 StringBuffer 之间的区别吧。 王铁牛:String 是不可变的,一旦创建其值不能被改变。StringBuilder 和 StringBuffer 是可变的。StringBuilder 是非线程安全的,性能相对较高;StringBuffer 是线程安全的,性能相对低一些,因为它的方法有同步机制。 面试官:非常棒,你的基础很扎实。

第二轮面试 面试官:接下来我们聊聊 JUC(Java Util Concurrency)相关的内容。你能说一下 JUC 里常用的类有哪些吗? 王铁牛:嗯……有 ReentrantLock,它是一个可重入锁,比 synchronized 更灵活。还有 CountDownLatch,它可以让一个或多个线程等待其他线程完成操作。 面试官:那 CountDownLatch 的使用场景能举个例子吗? 王铁牛:比如说,有一个大型的任务需要多个子任务并行执行,主线程需要等待所有子任务都完成后才能继续执行,这时候就可以用 CountDownLatch。子任务完成一个就调用一次 countDown 方法,主线程调用 await 方法等待计数器归零。 面试官:不错。再问你,CyclicBarrier 和 CountDownLatch 有什么区别呢? 王铁牛:这个……好像……它们都能实现线程间的同步,区别嘛,我有点不太确定了。 面试官:没关系,这确实有点复杂。简单来说,CountDownLatch 是一次性的,计数器减到 0 后就不能再用了;而 CyclicBarrier 可以循环使用,当所有线程到达屏障后,屏障可以重置。

第三轮面试 面试官:现在我们谈谈 Spring 框架。Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 是把对象的创建和依赖关系的管理交给 Spring 容器,AOP 是在不修改源代码的情况下,对程序进行增强。 面试官:那 Spring 中 Bean 的作用域有哪些? 王铁牛:有 singleton(单例)、prototype(原型)、request、session 等。singleton 作用域的 Bean 在 Spring 容器中只有一个实例,prototype 每次请求都会创建一个新的实例。 面试官:很好。那在 Spring 中如何解决循环依赖问题呢? 王铁牛:这个……我好像记得和三级缓存有关,但具体怎么回事我不太清楚了。 面试官:嗯,Spring 通过三级缓存来解决循环依赖问题。一级缓存是单例池,二级缓存是早期单例对象池,三级缓存是单例工厂。在创建 Bean 的过程中,会先将 Bean 的工厂放入三级缓存,在需要的时候从三级缓存中获取并放入二级缓存,最终放入一级缓存。

面试接近尾声,面试官看着王铁牛说:“今天的面试就到这里了。你在一些基础问题上回答得很不错,说明你有一定的知识储备,但在一些复杂问题上还存在不足,对于 JUC 和 Spring 的深入理解还需要加强。你先回家等通知吧,我们后续会综合评估后给你反馈。”

问题答案详细解析

  1. Java 中的基本数据类型有哪些
    • Java 有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(理论上 1 位,但 JVM 通常按 1 字节处理)。这些基本数据类型是 Java 语言最基础的数据表示方式,直接存储数据值。
  2. 在 Java 里,String 是基本数据类型吗,为什么
    • String 不是基本数据类型,而是一个类。基本数据类型是 Java 语言内置的简单数据类型,用于存储基本的数据值。而 String 类在 java.lang 包下,它提供了很多方法来操作字符串,比如 substring、equals、length 等。它的对象是通过 new 关键字或者字符串字面量创建的,存储的是字符序列,并且具有类的属性和行为。
  3. String、StringBuilder 和 StringBuffer 之间的区别
    • String 是不可变的,当对 String 对象进行修改时,实际上是创建了一个新的 String 对象。这是因为 String 类内部使用 final 修饰的字符数组来存储字符串,一旦创建,数组的内容不能改变。例如:
String str = "hello";
str = str + " world"; 

这里其实是创建了一个新的 String 对象 "hello world",原来的 "hello" 对象仍然存在于内存中。 - StringBuilder 和 StringBuffer 是可变的,它们内部使用可变的字符数组来存储字符串。当对它们进行修改时,不会创建新的对象,而是在原数组上进行操作。 - StringBuilder 是非线程安全的,它的方法没有同步机制,所以在单线程环境下性能较高。 - StringBuffer 是线程安全的,它的方法使用了 synchronized 关键字进行同步,保证了在多线程环境下操作的安全性,但性能相对较低。 4. JUC 里常用的类有哪些: - ReentrantLock:可重入锁,它比 synchronized 更灵活。它可以实现公平锁和非公平锁,并且可以通过 lock() 和 unlock() 方法手动控制锁的获取和释放。例如:

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock();
}
- CountDownLatch:它是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。它使用一个计数器来实现,初始值为线程的数量。每个线程完成任务后调用 countDown() 方法将计数器减 1,当计数器为 0 时,等待的线程可以继续执行。
- CyclicBarrier:它也是一个同步辅助类,允许一组线程相互等待,直到所有线程都到达一个屏障点,然后所有线程可以继续执行。与 CountDownLatch 不同的是,CyclicBarrier 可以循环使用,当所有线程到达屏障后,屏障可以重置。

5. CountDownLatch 的使用场景能举个例子吗: - 比如在一个大型的数据分析系统中,有一个主任务需要等待多个子任务完成数据采集后才能进行数据分析。每个子任务负责从不同的数据源采集数据,主任务就可以使用 CountDownLatch 来等待所有子任务完成。示例代码如下:

import java.util.concurrent.CountDownLatch;

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

        for (int i = 0; i < taskCount; i++) {
            final int taskId = i;
            new Thread(() -> {
                try {
                    System.out.println("Task " + taskId + " is running.");
                    Thread.sleep(1000); 
                    System.out.println("Task " + taskId + " is completed.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        latch.await(); 
        System.out.println("All tasks are completed. Start data analysis.");
    }
}
  1. CyclicBarrier 和 CountDownLatch 有什么区别
    • 作用不同:CountDownLatch 主要用于一个或多个线程等待其他线程完成操作;CyclicBarrier 用于一组线程相互等待,直到所有线程都到达一个屏障点。
    • 计数器使用方式不同:CountDownLatch 的计数器只能使用一次,一旦计数器减到 0 就不能再用了;CyclicBarrier 的计数器可以循环使用,当所有线程到达屏障后,计数器会重置。
    • 实现原理不同:CountDownLatch 基于 AQS(AbstractQueuedSynchronizer)的共享模式实现;CyclicBarrier 基于 ReentrantLock 和 Condition 实现。
  2. Spring 的核心特性有哪些
    • IoC(控制反转):也称为依赖注入(DI),是 Spring 框架的核心特性之一。它将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。通过 Spring 配置文件或者注解,Spring 容器负责创建对象并将对象之间的依赖关系注入进去。例如,一个 Service 类依赖一个 Dao 类,在传统的编程中,Service 类需要自己创建 Dao 类的实例;而在 Spring 中,Service 类只需要声明对 Dao 类的依赖,Spring 容器会自动创建并注入 Dao 类的实例。
    • AOP(面向切面编程):它允许在不修改源代码的情况下,对程序进行增强。AOP 主要用于处理一些横切关注点,如日志记录、事务管理、权限验证等。在 Spring 中,通过定义切面(Aspect)、切入点(Pointcut)和通知(Advice)来实现 AOP。切面是包含通知和切入点的类,切入点定义了哪些方法需要被增强,通知定义了在何时(前置、后置、环绕等)执行增强逻辑。
  3. Spring 中 Bean 的作用域有哪些
    • singleton:单例作用域,Spring 容器中只会创建一个 Bean 实例,所有对该 Bean 的引用都指向同一个实例。这是 Spring 中默认的 Bean 作用域。
    • prototype:原型作用域,每次请求该 Bean 时,Spring 容器都会创建一个新的实例。
    • request:在 Web 应用中,每个 HTTP 请求都会创建一个新的 Bean 实例,该实例仅在当前请求的生命周期内有效。
    • session:在 Web 应用中,每个 HTTP 会话都会创建一个新的 Bean 实例,该实例在当前会话的生命周期内有效。
  4. 在 Spring 中如何解决循环依赖问题
    • Spring 通过三级缓存来解决循环依赖问题。
    • 一级缓存:singletonObjects,也称为单例池,存储已经创建好的单例 Bean 实例。
    • 二级缓存:singletonFactories,存储单例工厂对象,这些工厂对象可以创建早期的单例 Bean 实例。
    • 三级缓存:earlySingletonObjects,存储早期的单例 Bean 实例,这些实例还没有完全初始化完成。
    • 当一个 Bean 被创建时,首先会将它的单例工厂对象放入三级缓存。如果在创建过程中发现有循环依赖,会从三级缓存中获取该 Bean 的早期实例,放入二级缓存。最终,当 Bean 初始化完成后,会将其从二级缓存移除,放入一级缓存。这样就解决了循环依赖问题。例如,当 Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A 时,在创建 Bean A 的过程中会将 A 的单例工厂放入三级缓存,在创建 Bean B 时发现依赖 A,就会从三级缓存中获取 A 的早期实例,完成 B 的创建,进而完成 A 的创建。