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

87 阅读8分钟

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

昏暗的会议室里,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。面试开始,一场技术的较量就此展开。

第一轮提问 面试官:先来几个基础的 Java 核心知识问题。Java 中基本数据类型有哪些? 王铁牛:嗯,有 byte、short、int、long、float、double、char、boolean。 面试官:回答正确,基础很扎实。那说说 Java 中的封装、继承和多态是什么? 王铁牛:封装就是把数据和操作数据的方法绑定在一起,隐藏内部实现细节;继承是子类继承父类的属性和方法;多态就是一个对象可以有多种表现形式,比如父类引用指向子类对象。 面试官:非常棒。那 String、StringBuilder 和 StringBuffer 有什么区别? 王铁牛:String 是不可变的,每次对 String 的操作都会生成新的对象;StringBuilder 是非线程安全的,效率高;StringBuffer 是线程安全的,效率相对低一些。 面试官:回答得很清晰,看来你对 Java 核心知识掌握得不错。

第二轮提问 面试官:接下来聊聊 JUC 和多线程相关的。什么是线程池? 王铁牛:线程池就是预先创建好一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,执行完后线程不会销毁,而是放回线程池。 面试官:回答正确。那线程池的核心参数有哪些? 王铁牛:这个嘛……有核心线程数、最大线程数,还有……其他的我有点记不清了。 面试官:核心线程数、最大线程数、线程空闲时间、任务队列和拒绝策略这几个参数还是需要牢记的。那说说 synchronized 和 ReentrantLock 的区别。 王铁牛:嗯……它们好像都是用来实现线程同步的,其他的我不太清楚了。 面试官:synchronized 是 Java 内置的关键字,使用起来比较简单,由 JVM 来管理锁的获取和释放;ReentrantLock 是一个类,需要手动加锁和解锁,功能更强大一些。你这方面还需要加强学习。

第三轮提问 面试官:我们再谈谈框架相关的。Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:很好。那 Spring Boot 是如何简化 Spring 开发的? 王铁牛:呃……好像是有自动配置什么的,具体我也说不太清楚。 面试官:Spring Boot 通过自动配置和起步依赖,大大减少了 Spring 项目的配置文件,让开发更加便捷。那 MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:这个……我有点搞混了,好像一个是预编译,一个不是,具体哪个是哪个我忘了。 **面试官**:#{} 是预编译处理,能防止 SQL 注入;{} 是字符串替换,会有 SQL 注入风险。你对框架的理解还不够深入。

面试接近尾声,面试官整理了下手中的资料,说道:“今天的面试就到这里,你回去等通知吧。通过这三轮面试,能看出你对 Java 核心知识有一定的掌握,回答一些基础问题表现不错,这说明你有一定的知识储备。但在 JUC、多线程、框架等方面,你还有明显的不足,对于一些关键的技术点理解不够清晰和深入,像线程池参数、同步锁区别、框架特性的应用等。后续你可以针对性地加强学习,提升自己的技术能力。我们会综合考量后尽快给你答复。”

答案详解

  1. Java 基本数据类型:Java 中有 8 种基本数据类型,可分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(理论上 1 位,但 JVM 通常按 1 字节处理)。
  2. 封装、继承和多态
    • 封装:将数据和操作数据的方法捆绑在一起,通过访问控制修饰符(如 private、protected、public)隐藏对象的内部实现细节,只对外提供必要的接口,提高了代码的安全性和可维护性。例如,一个类的属性设为 private,通过 public 的 getter 和 setter 方法来访问和修改属性。
    • 继承:子类继承父类的属性和方法,使得子类可以复用父类的代码,同时可以扩展新的功能。子类可以重写父类的方法,实现自己的逻辑。通过继承可以实现代码的复用和层次化组织。
    • 多态:指同一个行为具有多个不同表现形式或形态的能力。主要通过方法重载(同一个类中,方法名相同但参数列表不同)和方法重写(子类重写父类的方法)以及父类引用指向子类对象来实现。多态使得代码更加灵活和可扩展。
  3. String、StringBuilder 和 StringBuffer
    • String:是不可变的,一旦创建,其值不能被修改。每次对 String 进行操作(如拼接、替换等),都会创建一个新的 String 对象,会消耗较多的内存和性能。
    • StringBuilder:是可变的,非线程安全的。它内部使用一个可变的字符数组来存储字符序列,在进行字符串拼接等操作时,不会创建新的对象,而是直接在原数组上进行修改,效率较高。适用于单线程环境。
    • StringBuffer:也是可变的,但它是线程安全的。在多线程环境下,对 StringBuffer 的操作会进行同步处理,保证线程安全,但效率相对较低。
  4. 线程池:线程池是一种线程管理机制,预先创建一定数量的线程并保存在线程池中。当有任务提交时,从线程池中获取空闲线程来执行任务,任务执行完后线程不会销毁,而是返回线程池等待下一个任务。线程池可以减少线程创建和销毁的开销,提高系统的性能和资源利用率。
  5. 线程池的核心参数
    • 核心线程数:线程池保持的最小线程数量,即使这些线程处于空闲状态,也不会被销毁。
    • 最大线程数:线程池允许创建的最大线程数量。当任务队列满了,并且线程数达到核心线程数时,会创建新的线程,直到达到最大线程数。
    • 线程空闲时间:当线程空闲时间超过这个值时,多余的线程(超过核心线程数的部分)会被销毁。
    • 任务队列:用于存储等待执行的任务。当提交的任务数超过核心线程数时,任务会被放入任务队列中等待执行。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
    • 拒绝策略:当任务队列满了,并且线程数达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程来执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  6. synchronized 和 ReentrantLock 的区别
    • 语法层面:synchronized 是 Java 内置的关键字,使用起来比较简单,不需要手动释放锁,由 JVM 自动管理锁的获取和释放;ReentrantLock 是一个类,需要手动调用 lock() 方法获取锁,unlock() 方法释放锁,通常需要在 finally 块中释放锁,以确保锁一定会被释放。
    • 锁的特性:synchronized 是非公平锁,即线程获取锁的顺序是不确定的;ReentrantLock 可以是公平锁也可以是非公平锁,通过构造函数传入 true 可以创建公平锁,公平锁会按照线程请求锁的顺序来分配锁。
    • 锁的功能:ReentrantLock 提供了更多的功能,如可中断锁(lockInterruptibly() 方法)、尝试获取锁(tryLock() 方法)等,而 synchronized 不具备这些功能。
  7. Spring 的核心特性
    • IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到外部容器中。Spring 容器负责创建对象并注入对象之间的依赖关系,使得对象之间的耦合度降低,提高了代码的可维护性和可测试性。
    • AOP(面向切面编程):是一种编程范式,它允许在不修改原有代码的基础上,对程序的功能进行增强。通过将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,以切面的形式进行统一管理。Spring AOP 基于代理模式实现,有 JDK 动态代理和 CGLIB 代理两种方式。
  8. Spring Boot 简化 Spring 开发的方式
    • 自动配置:Spring Boot 通过自动配置机制,根据项目的依赖和配置,自动为应用程序配置合适的 Bean。例如,当项目中引入了 Spring Data JPA 和数据库驱动时,Spring Boot 会自动配置数据源、JPA 实体管理器等。
    • 起步依赖:Spring Boot 提供了一系列的起步依赖,这些依赖将常用的库和框架组合在一起,开发者只需要引入相应的起步依赖,就可以快速搭建项目。例如,引入 spring-boot-starter-web 依赖,就可以快速搭建一个基于 Spring MVC 的 Web 应用。
    • 嵌入式服务器:Spring Boot 内置了嵌入式服务器(如 Tomcat、Jetty 等),不需要手动部署 WAR 包到服务器,只需要运行 Spring Boot 应用的主类,就可以启动服务器并运行应用。
  9. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符?,然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入,因为 PreparedStatement 会对输入的参数进行类型检查和转义处理。
    • **:是字符串替换,MyBatis在处理{}**:是字符串替换,MyBatis 在处理 {} 时,会直接将 替换为传入的参数值。如果传入的参数包含SQL注入的恶意代码,就会导致SQL注入风险。通常{} 替换为传入的参数值。如果传入的参数包含 SQL 注入的恶意代码,就会导致 SQL 注入风险。通常 {} 用于动态表名、列名等情况,但使用时需要谨慎。