互联网大厂 Java 面试:核心知识、框架与中间件大考验
王铁牛怀揣着对互联网大厂的向往,走进了面试房间。严肃的面试官坐在对面,一场激烈的技术问答即将展开。
第一轮提问 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那说说 String 类为什么是不可变的? 王铁牛:因为 String 类是用 final 修饰的,它的底层是用 final 修饰的字符数组存储数据,所以不可变。 面试官:很好。那在 Java 中,== 和 equals 方法有什么区别? 王铁牛:== 比较的是对象的引用,如果是基本数据类型,比较的是值。而 equals 方法默认比较的也是引用,但很多类重写了 equals 方法,用来比较对象的内容。 面试官:非常棒,看来你对 Java 核心知识掌握得很扎实。接下来看看多线程方面,什么是线程安全?
第二轮提问 面试官:我们接着聊 JUC 和线程池相关的。JUC 包是什么? 王铁牛:JUC 就是 java.util.concurrent 包,里面提供了很多用于并发编程的工具类。 面试官:不错。那线程池有哪些创建方式? 王铁牛:可以通过 Executors 工具类创建,像 newFixedThreadPool、newCachedThreadPool 等,也可以通过 ThreadPoolExecutor 构造函数创建。 面试官:那说说 ThreadPoolExecutor 的几个重要参数。 王铁牛:有 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程存活时间)、unit(时间单位)、workQueue(工作队列)、threadFactory(线程工厂)、handler(拒绝策略)。 面试官:看来你对线程池也有一定了解。那在高并发场景下,线程池如何优化配置?
第三轮提问 面试官:现在聊聊一些框架和中间件。Spring 框架的核心特性有哪些? 王铁牛:Spring 核心特性有 IOC(控制反转)和 AOP(面向切面编程)。 面试官:很好。那 Spring Boot 相比 Spring 有什么优势? 王铁牛:Spring Boot 简化了 Spring 项目的配置,它有自动配置功能,能快速搭建项目,还内置了服务器。 面试官:那 MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译处理,能防止 SQL 注入,而 {} 是字符串替换,可能存在 SQL 注入风险。 面试官:看来你对这些框架有一定认识。那 Dubbo 框架的主要功能有哪些? 王铁牛:Dubbo 主要功能有远程调用、服务注册与发现、负载均衡等。不过在说具体原理和业务场景结合时,王铁牛回答得就有些含糊不清了。 面试官:最后问一下,Redis 在高并发场景下如何保证数据一致性?
王铁牛这次回答得结结巴巴,没有清晰的思路。
面试官:今天的面试就到这里,你先回家等通知吧。我们会综合评估后给你反馈。
问题答案详细解析
- Java 基本数据类型:Java 有 8 种基本数据类型,byte 是 8 位有符号整数,取值范围 -128 到 127;short 是 16 位有符号整数;int 是 32 位有符号整数;long 是 64 位有符号整数,使用时数字后面要加 L;float 是 32 位单精度浮点数,数字后面要加 F;double 是 64 位双精度浮点数;char 是 16 位 Unicode 字符;boolean 只有两个值 true 和 false。
- String 类不可变的原因:String 类被 final 修饰,意味着它不能被继承。其底层是用 final 修饰的字符数组
private final char value[]存储数据,一旦初始化,这个数组的引用不能被改变,而且 String 类没有提供修改字符数组内容的方法,所以 String 对象是不可变的。不可变的好处有安全性、可缓存哈希码、线程安全等。 - == 和 equals 方法的区别:对于基本数据类型,== 比较的是它们的值是否相等。对于引用类型,== 比较的是两个引用是否指向同一个对象。而 Object 类的 equals 方法默认实现是
return (this == obj);,也是比较引用。但很多类(如 String、Integer 等)重写了 equals 方法,用来比较对象的内容是否相等。 - 线程安全:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。例如 String 类就是线程安全的,因为它是不可变的,多个线程同时访问不会出现数据不一致的问题。
- JUC 包:java.util.concurrent 包是 Java 提供的用于并发编程的工具包,里面包含了很多实用的类和接口。比如 Executors 类用于创建线程池,ReentrantLock 可重入锁,CountDownLatch 倒计时器等,这些工具能帮助开发者更方便地进行多线程编程。
- 线程池的创建方式:
- 通过 Executors 工具类:
newFixedThreadPool:创建一个固定大小的线程池,核心线程数和最大线程数相等,使用 LinkedBlockingQueue 作为工作队列。newCachedThreadPool:创建一个可缓存的线程池,核心线程数为 0,最大线程数为 Integer.MAX_VALUE,使用 SynchronousQueue 作为工作队列,线程空闲 60 秒会被回收。newSingleThreadExecutor:创建一个单线程的线程池,核心线程数和最大线程数都为 1,使用 LinkedBlockingQueue 作为工作队列。
- 通过 ThreadPoolExecutor 构造函数:可以根据具体需求灵活配置线程池的参数。
- 通过 Executors 工具类:
- ThreadPoolExecutor 的重要参数:
corePoolSize:线程池的核心线程数,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。maximumPoolSize:线程池允许的最大线程数,当工作队列满了且提交的任务数超过核心线程数时,线程池会创建新的线程,直到达到最大线程数。keepAliveTime:当线程池中的线程数量超过核心线程数时,多余的空闲线程在终止之前等待新任务的最长时间。unit:keepAliveTime的时间单位,如 TimeUnit.SECONDS 表示秒。workQueue:用于存储等待执行的任务的队列,常见的有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。threadFactory:用于创建线程的工厂,可以自定义线程的名称、优先级等。handler:当工作队列满了且线程数达到最大线程数时,新提交的任务的处理策略,常见的有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让调用者线程执行任务)等。
- 高并发场景下线程池的优化配置:首先要根据业务场景确定核心线程数和最大线程数。如果是 CPU 密集型任务,核心线程数可以设置为 CPU 核心数 + 1;如果是 IO 密集型任务,核心线程数可以设置得大一些,如 2 * CPU 核心数。选择合适的工作队列,对于任务执行时间短、数量大的场景,可以使用 SynchronousQueue;对于任务执行时间长、数量少的场景,可以使用 LinkedBlockingQueue。同时,根据实际情况选择合适的拒绝策略。
- Spring 框架的核心特性:
- IOC(控制反转):也叫依赖注入,是一种设计思想。传统的对象创建和依赖关系由程序代码控制,而在 IOC 中,对象的创建和依赖关系的管理由 Spring 容器负责。通过 IOC,降低了代码的耦合度,提高了可维护性。
- AOP(面向切面编程):允许开发者在不修改原有业务逻辑的基础上,对程序进行增强。例如在方法执行前后添加日志记录、事务管理等功能。AOP 的实现原理主要有动态代理(JDK 动态代理和 CGLIB 动态代理)。
- Spring Boot 相比 Spring 的优势:Spring Boot 简化了 Spring 项目的配置,它通过自动配置功能,根据项目中引入的依赖自动配置 Spring 应用。例如引入 Spring Data JPA 依赖,Spring Boot 会自动配置数据源、JPA 等相关组件。它还内置了服务器(如 Tomcat、Jetty 等),可以直接将项目打包成可执行的 JAR 文件运行,无需部署到外部服务器。此外,Spring Boot 提供了很多 Starter 依赖,方便开发者快速集成各种功能。
- **MyBatis 中 #{} 和 {} 的区别**:#{} 是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符?,然后使用 PreparedStatement 进行预编译,能有效防止 SQL 注入。而 {} 是字符串替换,MyBatis 会直接将 {},但要注意对输入进行严格的校验。
- Dubbo 框架的主要功能:
- 远程调用:Dubbo 提供了高效的远程调用机制,允许不同服务之间进行跨网络的方法调用,就像调用本地方法一样方便。
- 服务注册与发现:Dubbo 支持多种注册中心(如 ZooKeeper、Nacos 等),服务提供者将自己的服务信息注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,实现服务的发现和调用。
- 负载均衡:Dubbo 提供了多种负载均衡策略(如随机、轮询、最少活跃调用数等),当有多个服务提供者时,Dubbo 会根据负载均衡策略选择一个合适的服务提供者进行调用,提高系统的性能和可用性。
- Redis 在高并发场景下保证数据一致性:
- 读写分离场景:使用主从复制,主节点负责写操作,从节点负责读操作。主节点将写操作同步到从节点,但存在一定的延迟。为了减少延迟,可以采用一些策略,如对实时性要求高的读操作直接从主节点读取。
- 分布式锁:使用 Redis 的 SETNX 命令或 Redisson 等工具实现分布式锁,保证在高并发场景下对共享资源的访问是互斥的,避免数据不一致。例如在多个线程同时修改同一个数据时,先获取分布式锁,修改完成后释放锁。
- 缓存更新策略:采用合适的缓存更新策略,如缓存失效、缓存更新、缓存删除等。可以使用双写一致性策略,在更新数据库的同时更新缓存;也可以使用异步更新策略,先更新数据库,然后异步更新缓存。同时,要注意处理缓存击穿、缓存雪崩等问题。