互联网大厂 Java 面试:核心知识、并发、框架与中间件大考验
在互联网大厂的一间安静的面试室内,严肃的面试官坐在桌前,对面坐着紧张又期待的求职者王铁牛。一场对 Java 核心知识的严峻考验即将拉开帷幕。
第一轮提问 面试官:首先问几个基础的 Java 核心知识问题。Java 中的基本数据类型有哪些? 王铁牛:嗯,有 byte、short、int、long、float、double、char、boolean 这八种。 面试官:回答正确,不错。那说说 Java 中多态的实现方式有哪些? 王铁牛:多态的实现方式主要有继承和接口,通过方法重写和方法重载来体现多态性。 面试官:很好,理解得很清晰。那 String、StringBuilder 和 StringBuffer 有什么区别? 王铁牛:String 是不可变的,每次对 String 进行操作都会生成一个新的 String 对象。而 StringBuilder 和 StringBuffer 是可变的,StringBuffer 是线程安全的,StringBuilder 是非线程安全的,在单线程环境下 StringBuilder 性能更好。 面试官:非常棒,基础很扎实。
第二轮提问 面试官:接下来聊聊 JUC 和多线程相关的。什么是线程池?为什么要使用线程池? 王铁牛:线程池就是管理线程的一个池子,使用线程池可以减少线程创建和销毁的开销,提高性能,还能控制并发线程的数量。 面试官:回答得很准确。那线程池有哪些常见的创建方式? 王铁牛:可以通过 Executors 工具类创建,像 newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor 这些。 面试官:没错。那在多线程环境下,如何保证线程安全? 王铁牛:可以使用 synchronized 关键字、Lock 接口、原子类这些来保证线程安全。 面试官:很好,对多线程这块掌握得不错。
第三轮提问 面试官:现在考察一下框架相关的。Spring 框架的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:那 Spring Boot 相比 Spring 有什么优势? 王铁牛:Spring Boot 简化了 Spring 的配置,有自动配置功能,能快速搭建项目,还内置了服务器。 面试官:接着说说 MyBatis 的工作原理。 王铁牛:嗯……这个……大概就是通过配置文件和映射文件来实现数据库操作,具体的我有点说不太清。 面试官:没关系,那 Dubbo 是什么,它的主要作用是什么? 王铁牛:Dubbo 是个分布式服务框架,能实现服务的注册和发现,不过具体怎么用我不太清楚。 面试官:看来你对框架的原理和应用还需要进一步学习。
面试接近尾声,面试官整理了一下桌上的资料,看着王铁牛说:“今天的面试就到这里,你的基础知识掌握得还可以,在 Java 核心知识、多线程和线程池方面回答得比较准确,表现不错。但在框架和中间件的深入理解和应用上还有所欠缺,比如 MyBatis 的工作原理和 Dubbo 的具体使用你都没有清晰的阐述。我们会综合评估你的整体表现,你先回家等通知吧。”
答案详解
- Java 中的基本数据类型:
- Java 有八种基本数据类型,分为四大类。
- 整数类型:byte(1 字节,-128 到 127)、short(2 字节,-32768 到 32767)、int(4 字节,-2147483648 到 2147483647)、long(8 字节,范围更大)。
- 浮点类型:float(4 字节,单精度浮点数)、double(8 字节,双精度浮点数)。
- 字符类型:char(2 字节,用于表示单个字符,采用 Unicode 编码)。
- 布尔类型:boolean(只有 true 和 false 两个值)。
- Java 中多态的实现方式:
- 继承:子类继承父类,并重写父类的方法。当通过父类引用指向子类对象时,调用重写的方法会根据实际对象类型来执行相应的方法。例如:
class Animal {
public void makeSound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
// 使用
Animal animal = new Dog();
animal.makeSound(); // 输出 Dog barks
- **接口**:类实现接口,并实现接口中的抽象方法。通过接口引用指向实现类的对象,调用接口方法时会执行实现类的具体实现。
- **方法重载**:在同一个类中,方法名相同但参数列表不同(参数个数、类型、顺序不同),编译器会根据调用时传入的参数来选择合适的方法执行。
3. String、StringBuilder 和 StringBuffer 的区别: - String:是不可变的对象,一旦创建,其值不能被修改。每次对 String 进行操作(如拼接、替换等)都会创建一个新的 String 对象,这会导致频繁的内存分配和垃圾回收,性能较低。例如:
String s = "Hello";
s = s + " World"; // 这里创建了新的 String 对象
- **StringBuilder**:是可变的对象,在进行字符串拼接等操作时,不会创建新的对象,而是在原对象的基础上进行修改,性能较高。但它是非线程安全的,适用于单线程环境。
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 直接在原对象上修改
- **StringBuffer**:也是可变的对象,与 StringBuilder 类似,但它是线程安全的,内部的方法大多使用了 synchronized 关键字进行同步。由于同步会带来一定的性能开销,所以在单线程环境下性能不如 StringBuilder。
4. 线程池: - 定义:线程池是一种管理线程的机制,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完后线程不会销毁,而是返回线程池等待下一个任务。 - 使用原因: - 减少线程创建和销毁的开销:频繁创建和销毁线程会消耗大量的系统资源,使用线程池可以复用线程,提高性能。 - 控制并发线程的数量:可以避免创建过多的线程导致系统资源耗尽,通过线程池可以设置最大线程数,保证系统的稳定性。 - 提高响应速度:任务提交后可以立即从线程池中获取线程执行,无需等待线程创建。 5. 线程池常见的创建方式: - newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量始终保持不变。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列中等待。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
- **newCachedThreadPool**:创建一个可缓存的线程池,线程池中的线程数量会根据任务的数量自动调整。如果有新任务提交,而线程池中没有空闲线程,则会创建新的线程来执行任务;如果线程在一段时间内没有使用,则会被销毁。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- **newSingleThreadExecutor**:创建一个单线程的线程池,线程池中只有一个线程,所有任务会按照提交的顺序依次执行。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
- 多线程环境下保证线程安全的方法:
- synchronized 关键字:可以修饰方法或代码块,保证同一时刻只有一个线程可以访问被修饰的方法或代码块。例如:
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
}
- **Lock 接口**:提供了比 synchronized 更灵活的锁机制,例如 ReentrantLock。可以手动加锁和解锁,还支持可重入、公平锁等特性。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
- **原子类**:位于 java.util.concurrent.atomic 包下,如 AtomicInteger、AtomicLong 等。这些类使用了 CAS(Compare-And-Swap)算法,在多线程环境下可以保证对变量的原子操作,避免了使用锁带来的性能开销。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
}
- Spring 框架的核心特性:
- IoC(控制反转):也称为依赖注入(DI),是一种将对象的创建和依赖关系的管理从代码中转移到外部容器的机制。Spring 容器负责创建对象并注入对象之间的依赖关系,降低了代码的耦合度。例如,通过 XML 配置或注解的方式,让 Spring 容器来创建和管理对象。
- AOP(面向切面编程):允许开发者在不修改原有代码的基础上,对程序进行增强。通过将横切关注点(如日志记录、事务管理等)封装成切面,在特定的连接点(如方法调用、异常抛出等)插入切面代码,实现功能的扩展。
- Spring Boot 相比 Spring 的优势:
- 简化配置:Spring Boot 提供了自动配置功能,根据项目的依赖和配置,自动为开发者配置好 Spring 应用的各种组件,减少了大量的 XML 配置文件。
- 快速搭建项目:通过 Spring Initializr 可以快速生成项目骨架,包含了必要的依赖和配置,开发者可以立即开始编写业务代码。
- 内置服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,无需手动部署到外部服务器,直接运行项目即可启动服务器。
- MyBatis 的工作原理:
- 读取配置文件:MyBatis 首先会读取配置文件(如 mybatis-config.xml),该文件包含了数据库连接信息、映射文件的位置等配置。
- 创建 SqlSessionFactory:根据配置文件创建 SqlSessionFactory 对象,它是 MyBatis 的核心工厂类,用于创建 SqlSession 对象。
- 创建 SqlSession:SqlSession 是 MyBatis 与数据库交互的会话对象,通过 SqlSession 可以执行 SQL 语句。
- 执行 SQL:通过 SqlSession 调用映射文件中定义的 SQL 语句,MyBatis 会根据 SQL 语句和传入的参数,将 SQL 语句发送到数据库执行,并将查询结果映射到 Java 对象中。
- 关闭 SqlSession:操作完成后,需要关闭 SqlSession,释放资源。
- Dubbo 是什么,主要作用是什么:
- 定义:Dubbo 是阿里巴巴开源的分布式服务框架,用于解决分布式系统中服务之间的调用和管理问题。
- 主要作用:
- 服务注册与发现:Dubbo 提供了服务注册中心(如 ZooKeeper、Nacos 等),服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,实现服务的发现和调用。
- 远程调用:Dubbo 支持多种远程调用协议(如 Dubbo 协议、HTTP 协议等),可以实现不同服务之间的远程调用,屏蔽了网络通信的细节。
- 负载均衡:Dubbo 提供了多种负载均衡策略(如随机、轮询、最少活跃调用数等),可以根据服务提供者的性能和负载情况,将请求分发到合适的服务提供者上,提高系统的性能和可用性。