互联网大厂面试:从 Java 核心到分布式技术的挑战之旅
在一间宽敞明亮却略显严肃的面试室内,一位穿着整洁西装、神情冷峻的面试官坐在桌前,对面是紧张又期待的求职者王铁牛。面试即将开始,一场技术能力的大考验拉开了帷幕。
第一轮提问 面试官:“首先,我们从 Java 核心知识开始。能说一下 Java 中基本数据类型有哪些吗?” 王铁牛:“这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char 和 boolean。” 面试官:“回答正确,基础很扎实。那 String 是基本数据类型吗?” 王铁牛:“不是,String 是引用数据类型,它是一个类。” 面试官:“不错。那说说 Java 中多态的实现方式有哪些?” 王铁牛:“多态的实现方式主要有继承和接口。通过父类引用指向子类对象,或者接口引用指向实现类对象,然后调用重写的方法就能实现多态。” 面试官:“回答得很好,看来你对 Java 核心知识掌握得挺不错。”
第二轮提问 面试官:“接下来聊聊 JUC 和多线程。什么是线程安全?” 王铁牛:“线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致或者其他异常情况。” 面试官:“那 JUC 包中的 CountDownLatch 有什么作用,举个业务场景例子。” 王铁牛:“CountDownLatch 可以让一个或多个线程等待其他线程完成操作。比如在一个电商系统中,有多个服务同时处理订单的不同信息,主线程需要等待这些服务都处理完后再进行后续的订单结算,就可以用 CountDownLatch。” 面试官:“有点思路了。那线程池有哪几种创建方式,分别适用于什么场景?” 王铁牛:“呃……有通过 Executors 工具类创建的几种,像 newFixedThreadPool 适用于需要限制线程数量的场景,还有……呃……其他的我有点记不太清了。” 面试官:“你提到了一部分,不过还不够全面,后续还得加强这方面的学习。”
第三轮提问 面试官:“现在我们谈谈一些框架。Spring 框架中 Bean 的生命周期是怎样的?” 王铁牛:“这个……我记得有实例化、属性赋值、初始化,然后是销毁。具体的细节我一下子想不起来了。” 面试官:“不太清晰啊。那 Dubbo 框架的工作原理能说一下吗?” 王铁牛:“Dubbo 就是做分布式服务调用的,呃……好像有注册中心,然后服务提供者和消费者,具体怎么交互我不太清楚。” 面试官:“看来你对框架的理解还比较浅。最后问一下,Redis 的持久化机制有哪些?” 王铁牛:“有……好像有 RDB 和 AOF,但是它们具体是怎么实现的我不太懂。”
面试官:“王铁牛,通过这次面试,能看出你对 Java 核心知识有一定的掌握,回答一些基础问题表现还不错。不过在 JUC、线程池、框架原理以及 Redis 等方面,你还有很多需要学习和深入理解的地方。你先回家等通知吧,后续如果有结果我们会及时联系你。”
问题答案
- Java 中基本数据类型有哪些?
- Java 有 8 种基本数据类型,分为 4 类:
- 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节)。
- 布尔类型:boolean(理论上 1 位,但在 Java 中没有明确规定大小)。
- Java 有 8 种基本数据类型,分为 4 类:
- String 是基本数据类型吗?
- String 不是基本数据类型,而是引用数据类型。它是 Java 中的一个类,位于 java.lang 包下。基本数据类型是 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();
}
}
- 接口:接口引用指向实现类对象,调用实现类实现的接口方法。例如:
interface Shape {
double area();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
Shape shape = new Circle(5);
System.out.println(shape.area());
}
}
- 什么是线程安全?
- 在多线程环境下,当多个线程同时访问共享资源时,如果对该资源的操作不会导致数据不一致、数据损坏或其他异常情况,那么就称该资源是线程安全的。例如,一个简单的计数器,如果多个线程同时对其进行自增操作,可能会出现数据错误,而通过加锁等方式保证操作的原子性,就能实现线程安全。
- JUC 包中的 CountDownLatch 有什么作用,举个业务场景例子。
- CountDownLatch 是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,初始化时设置一个初始值,每当一个线程完成任务,计数器就减 1,当计数器为 0 时,等待的线程就会被唤醒继续执行。
- 业务场景例子:在电商系统中,有多个服务同时处理订单的不同信息,比如商品信息查询、库存检查、用户信息验证等。主线程需要等待这些服务都处理完后再进行后续的订单结算。可以使用 CountDownLatch 来实现:
import java.util.concurrent.CountDownLatch;
public class OrderProcessing {
public static void main(String[] args) throws InterruptedException {
int serviceCount = 3;
CountDownLatch latch = new CountDownLatch(serviceCount);
// 模拟三个服务线程
for (int i = 0; i < serviceCount; i++) {
new Thread(() -> {
try {
// 模拟服务处理
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 服务处理完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
// 主线程等待所有服务处理完成
latch.await();
System.out.println("所有服务处理完成,进行订单结算");
}
}
- 线程池有哪几种创建方式,分别适用于什么场景?
- Java 中线程池的创建方式主要有以下几种:
- 通过 Executors 工具类创建:
newFixedThreadPool(int nThreads):创建一个固定大小的线程池,适用于需要限制线程数量的场景,比如服务器处理请求,为了避免过多线程导致资源耗尽。newCachedThreadPool():创建一个可缓存的线程池,线程数量会根据任务数量动态调整。适用于短时间内有大量任务需要处理的场景,因为它可以快速创建和回收线程。newSingleThreadExecutor():创建一个单线程的线程池,保证所有任务按照顺序依次执行。适用于需要保证任务顺序执行的场景,比如数据库操作,避免并发问题。newScheduledThreadPool(int corePoolSize):创建一个可以定时执行任务的线程池。适用于需要定时执行任务或者周期性执行任务的场景,比如定时数据备份。
- 通过 ThreadPoolExecutor 构造函数创建:这是最推荐的方式,可以自定义线程池的各种参数,根据具体业务需求进行调整。例如:
- 通过 Executors 工具类创建:
- Java 中线程池的创建方式主要有以下几种:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CustomThreadPool {
public static void main(String[] args) {
int corePoolSize = 5;
int maximumPoolSize = 10;
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ExecutorService executorService = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue
);
// 提交任务
executorService.submit(() -> System.out.println("Task executed"));
// 关闭线程池
executorService.shutdown();
}
}
- Spring 框架中 Bean 的生命周期是怎样的?
- Spring 中 Bean 的生命周期主要包括以下几个阶段:
- 实例化:通过反射机制创建 Bean 的实例。
- 属性赋值:为 Bean 的属性注入值,可以通过 XML 配置、注解等方式进行依赖注入。
- 初始化:
- 实现
BeanNameAware接口,调用setBeanName方法,设置 Bean 的名称。 - 实现
BeanFactoryAware接口,调用setBeanFactory方法,设置 Bean 工厂。 - 实现
ApplicationContextAware接口,调用setApplicationContext方法,设置应用上下文。 - 调用
BeanPostProcessor的postProcessBeforeInitialization方法,在初始化前进行一些处理。 - 实现
InitializingBean接口,调用afterPropertiesSet方法,进行一些初始化操作。 - 调用自定义的初始化方法(通过
init-method属性指定)。 - 调用
BeanPostProcessor的postProcessAfterInitialization方法,在初始化后进行一些处理。
- 实现
- 使用:Bean 可以被应用程序使用。
- 销毁:
- 实现
DisposableBean接口,调用destroy方法,进行一些销毁操作。 - 调用自定义的销毁方法(通过
destroy-method属性指定)。
- 实现
- Spring 中 Bean 的生命周期主要包括以下几个阶段:
- Dubbo 框架的工作原理能说一下吗?
- Dubbo 是一个高性能的分布式服务框架,其工作原理主要包括以下几个部分:
- 服务提供者:将自己提供的服务注册到注册中心,同时启动服务监听,等待服务消费者的调用请求。
- 注册中心:负责服务的注册和发现,保存服务提供者的元数据信息,如服务名称、地址等。常见的注册中心有 ZooKeeper、Nacos 等。
- 服务消费者:从注册中心获取服务提供者的信息,然后根据这些信息调用服务提供者的服务。在调用过程中,Dubbo 会进行负载均衡、远程调用等操作。
- 监控中心:负责收集服务的调用信息,如调用次数、响应时间等,方便进行性能监控和分析。
- 具体的调用流程如下:
- 服务提供者启动时,将服务信息注册到注册中心。
- 服务消费者启动时,从注册中心订阅服务信息。
- 当服务消费者需要调用服务时,根据注册中心返回的服务提供者信息,选择一个合适的服务提供者进行远程调用。
- 服务调用完成后,将调用信息发送到监控中心。
- Dubbo 是一个高性能的分布式服务框架,其工作原理主要包括以下几个部分:
- Redis 的持久化机制有哪些?
- Redis 有两种主要的持久化机制:
- RDB(Redis Database):
- 原理:RDB 是将 Redis 在某个时间点的数据快照保存到磁盘上的一个二进制文件中。可以通过手动执行
SAVE或BGSAVE命令,或者配置定期执行的方式来触发 RDB 持久化。 - 优点:文件紧凑,适合用于备份和灾难恢复;恢复速度快。
- 缺点:可能会丢失最后一次快照之后的数据;在进行快照操作时,会占用一定的系统资源。
- 原理:RDB 是将 Redis 在某个时间点的数据快照保存到磁盘上的一个二进制文件中。可以通过手动执行
- AOF(Append Only File):
- 原理:AOF 是将 Redis 的写操作以日志的形式追加到文件末尾。每当 Redis 执行一个写操作,就会将该操作记录到 AOF 文件中。
- 优点:数据安全性高,最多只会丢失 1 秒的数据(取决于配置的同步策略);日志文件是文本格式,易于理解和修改。
- 缺点:AOF 文件通常比 RDB 文件大;恢复速度相对较慢。
- Redis 可以同时使用 RDB 和 AOF 两种持久化机制,以提高数据的安全性和恢复效率。
- RDB(Redis Database):
- Redis 有两种主要的持久化机制: