互联网大厂 Java 面试:核心知识、框架与中间件大考验
王铁牛怀揣着对互联网大厂的向往,走进了这场决定命运的 Java 面试。严肃的面试官正襟危坐,一场激烈的技术问答即将展开。
第一轮提问
- 面试官:首先,我们从 Java 核心知识开始。Java 中基本数据类型有哪些?
- 王铁牛:这个我知道,有 byte、short、int、long、float、double、char、boolean。
- 面试官:不错,回答得很准确。那 String 是基本数据类型吗?
- 王铁牛:不是,String 是引用数据类型,它是一个类。
- 面试官:很好。那说说 Java 中多态的实现方式有哪些?
- 王铁牛:多态的实现方式主要有继承和接口。通过子类重写父类的方法,或者类实现接口并实现接口中的方法来实现多态。
- 面试官:非常棒,看来你对 Java 核心知识掌握得很扎实。
第二轮提问
- 面试官:接下来我们聊聊 JUC 和多线程。什么是线程安全?
- 王铁牛:线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致等问题。
- 面试官:嗯,理解得正确。那 JUC 包中常用的类有哪些?
- 王铁牛:有 ReentrantLock、CountDownLatch、CyclicBarrier 这些。
- 面试官:不错。那线程池的核心参数有哪些?
- 王铁牛:呃……有核心线程数、最大线程数,还有……还有啥来着,我有点记不清了。
- 面试官:没关系,这里还有任务队列、拒绝策略等参数。那你说说线程池的工作流程是怎样的?
- 王铁牛:这个……就是有任务来了就处理,处理不过来就放队列里,队列满了就增加线程,再不行就拒绝任务,大概是这样吧。
第三轮提问
- 面试官:现在来谈谈一些框架和中间件。Spring 框架的核心特性有哪些?
- 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。
- 面试官:很好。那 Spring Boot 相对于 Spring 有什么优势?
- 王铁牛:Spring Boot 简化了配置,有自动配置功能,能快速搭建项目。
- 面试官:没错。那 MyBatis 中 #{} 和 ${} 的区别是什么?
- 王铁牛:这个……好像一个是预编译,一个不是,但具体我也说不太清楚。
- 面试官:再问你,Dubbo 的主要功能有哪些?
- 王铁牛:Dubbo 嘛,就是做服务调用的,其他的我不太了解了。
面试接近尾声,面试官扶了扶眼镜,说道:“今天的面试就到这里了,你整体对一些基础和简单的知识点掌握得还可以,但对于一些复杂的技术点和细节回答得不够清晰和准确。我们需要综合评估后再给你答复,你回家等通知吧。”
问题答案
- Java 中基本数据类型有哪些?
- Java 中有 8 种基本数据类型,可分为 4 类:
- 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节)。
- 布尔类型:boolean(理论上 1 位,但通常由 JVM 实现决定)。
- 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");
}
}
// 使用
Animal animal = new Dog();
animal.sound(); // 输出 Dog barks
- **接口**:类实现接口,并实现接口中的抽象方法。通过接口的引用指向实现类的对象,调用接口方法时会执行实现类中的具体实现。例如:
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;
}
}
// 使用
Shape shape = new Circle(5);
System.out.println(shape.area());
- 什么是线程安全?
- 在多线程环境下,当多个线程同时访问共享资源时,如果不采取任何同步措施,可能会导致数据不一致、数据损坏等问题。线程安全就是指在多线程环境下,对共享资源的访问不会出现这些问题,程序的执行结果和单线程环境下的执行结果是一致的。例如,一个计数器变量,如果多个线程同时对其进行自增操作,可能会出现数据丢失的情况,而线程安全的计数器会通过同步机制保证操作的正确性。
- JUC 包中常用的类有哪些?
- ReentrantLock:可重入锁,是一个可重入的互斥锁,它和 synchronized 关键字类似,但提供了更灵活的锁机制,例如可以实现公平锁、可中断锁等。例如:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
- **CountDownLatch**:一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。例如,主线程等待多个子线程完成任务后再继续执行:
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);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("All threads have finished");
}
}
- **CyclicBarrier**:一个同步辅助类,它允许一组线程相互等待,直到所有线程都到达某个屏障点,然后所有线程再继续执行。例如:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("All threads have reached the barrier");
});
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
// 模拟线程执行任务
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " has reached the barrier");
barrier.await();
System.out.println(Thread.currentThread().getName() + " continues execution");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
- 线程池的核心参数有哪些?
- corePoolSize:核心线程数,线程池在创建后默认是没有线程的,当有任务提交时,会创建线程来执行任务,直到线程数达到核心线程数。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。
- keepAliveTime:线程空闲时间,当线程池中的线程数量超过核心线程数时,多余的空闲线程在经过一定的时间后会被销毁,这个时间就是 keepAliveTime。
- TimeUnit:时间单位,用于指定 keepAliveTime 的时间单位,例如 TimeUnit.SECONDS 表示秒。
- workQueue:任务队列,用于存储提交的任务。当线程池中的线程数量达到核心线程数后,新提交的任务会被放入任务队列中等待执行。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
- threadFactory:线程工厂,用于创建线程。可以通过自定义线程工厂来设置线程的名称、优先级等属性。
- RejectedExecutionHandler:拒绝策略,当任务队列已满且线程池中的线程数量达到最大线程数时,新提交的任务会被拒绝,此时会调用拒绝策略来处理这些任务。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由提交任务的线程来执行任务)等。
- 线程池的工作流程是怎样的?
- 当有新任务提交到线程池时:
- 如果线程池中的线程数量小于核心线程数,会创建一个新的线程来执行该任务。
- 如果线程池中的线程数量已经达到核心线程数,新任务会被放入任务队列中等待执行。
- 如果任务队列已满,且线程池中的线程数量小于最大线程数,会创建一个新的线程来执行该任务。
- 如果任务队列已满,且线程池中的线程数量已经达到最大线程数,新任务会被拒绝,调用拒绝策略来处理。
- 当线程池中的线程完成任务后,如果线程空闲时间超过了 keepAliveTime,且线程数量超过核心线程数,多余的空闲线程会被销毁。
- 当有新任务提交到线程池时:
- Spring 框架的核心特性有哪些?
- IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。通过 Spring 容器来创建和管理对象,对象之间的依赖关系由 Spring 容器自动注入,从而降低了代码的耦合度。例如,通过 XML 配置或注解的方式来定义 Bean 和它们之间的依赖关系:
// 定义一个接口
interface UserService {
void sayHello();
}
// 实现类
class UserServiceImpl implements UserService {
@Override
public void sayHello() {
System.out.println("Hello");
}
}
// 使用 Spring 注解配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
- **AOP(面向切面编程)**:是一种编程范式,它允许开发者在不修改原有代码的基础上,对程序的某些功能进行增强。AOP 主要通过切面(Aspect)、通知(Advice)、切点(Pointcut)等概念来实现。例如,在方法执行前后添加日志记录、事务管理等功能:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void beforeServiceMethod(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
@After("serviceMethods()")
public void afterServiceMethod(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
}
- Spring Boot 相对于 Spring 有什么优势?
- 简化配置:Spring Boot 提供了自动配置功能,它会根据项目中引入的依赖自动配置 Spring 应用,大大减少了繁琐的 XML 配置或 Java 配置代码。例如,引入 Spring Boot 的 Web 依赖后,它会自动配置嵌入式 Tomcat 服务器、Spring MVC 等。
- 快速搭建项目:通过 Spring Initializr 可以快速生成一个基本的 Spring Boot 项目结构,包含所需的依赖和配置文件,开发者可以直接开始编写业务代码。
- 内置服务器:Spring Boot 内置了多种嵌入式服务器,如 Tomcat、Jetty 等,无需手动部署到外部服务器,直接运行项目即可启动服务器。
- 监控和管理:Spring Boot Actuator 提供了丰富的监控和管理端点,方便开发者对应用进行监控和管理,如查看应用的健康状态、内存使用情况等。
- MyBatis 中 #{} 和 ${} 的区别是什么?
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 来执行 SQL 语句。这样可以有效防止 SQL 注入攻击,因为参数会被自动进行类型转换和转义处理。例如:
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是字符串替换,MyBatis 在处理 ${} 时,会直接将 ${} 替换为传入的参数值。这种方式不会进行预编译处理,存在 SQL 注入的风险。通常用于需要动态传入表名、列名等情况。例如:
<select id="getUsersByTable" parameterType="String" resultType="User">
SELECT * FROM ${tableName}
</select>
- Dubbo 的主要功能有哪些?
- 远程服务调用:Dubbo 提供了高效的远程服务调用机制,允许不同的服务之间通过网络进行通信和调用。服务提供者将自己的服务暴露出来,服务消费者可以通过接口直接调用远程服务,就像调用本地方法一样。
- 服务注册与发现:Dubbo 支持多种服务注册中心,如 ZooKeeper、Nacos 等。服务提供者在启动时会将自己的服务信息注册到服务注册中心,服务消费者在启动时会从服务注册中心获取服务提供者的地址信息,然后进行服务调用。
- 负载均衡:Dubbo 提供了多种负载均衡策略,如随机、轮询、最少活跃调用数等。当有多个服务提供者提供相同的服务时,Dubbo 会根据负载均衡策略选择一个合适的服务提供者进行调用,以提高系统的性能和可用性。
- 集群容错:Dubbo 提供了多种集群容错策略,如失败重试、快速失败、广播等。当服务调用出现失败时,Dubbo 会根据集群容错策略进行相应的处理,以保证服务的可靠性。
- 服务治理:Dubbo 提供了丰富的服务治理功能,如服务降级、服务限流、服务监控等。可以通过配置来实现对服务的动态管理和控制,提高系统的稳定性和可维护性。