《互联网大厂面试:Java 核心、JUC、JVM 等技术深度考察》

37 阅读2分钟

在互联网大厂的一间安静面试室内,严肃的面试官正对面坐着紧张的求职者王铁牛,一场关于 Java 技术的面试即将展开。

第一轮面试 面试官:先来问几个基础的。Java 中基本数据类型有哪些? 王铁牛:有 byte、short、int、long、float、double、char、boolean 这八种。 面试官:回答正确。那说说 ArrayList 和数组有什么区别? 王铁牛:数组的长度是固定的,而 ArrayList 的长度是可变的。数组可以存储基本数据类型和引用类型,ArrayList 只能存储引用类型,如果要存基本数据类型得用包装类。 面试官:不错。那在多线程环境下,ArrayList 是线程安全的吗? 王铁牛:不是,ArrayList 不是线程安全的。如果多个线程同时对 ArrayList 进行读写操作,可能会出现数据不一致或者抛出 ConcurrentModificationException 异常。 面试官:回答得很好,基础很扎实。

第二轮面试 面试官:进入 JUC 相关的问题。说说 CountDownLatch 的使用场景和原理。 王铁牛:这个……嗯……好像是用来控制线程的,原理嘛,我不太清楚了。 面试官:那换个问题,CyclicBarrier 和 CountDownLatch 有什么区别? 王铁牛:它们好像都和线程有关,具体区别我有点说不上来。 面试官:再问一个,ReentrantLock 和 synchronized 有什么不同? 王铁牛:我知道它们都能实现线程同步,但是具体不同我回答得不太清晰。 面试官:这几个问题回答得不太理想,后续要加强对 JUC 的学习。

第三轮面试 面试官:聊聊框架方面的。Spring 中的 IoC 和 AOP 是什么,能举例说明吗? 王铁牛:IoC 好像是控制反转,AOP 是面向切面编程。例子嘛,我有点想不起来合适的了。 面试官:那 Spring Boot 相比 Spring 有什么优势? 王铁牛:好像 Spring Boot 用起来更方便,配置更少,具体的我说不详细。 面试官:MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:我只知道它们都能传参数,区别不太清楚。 面试官:这一轮关于框架的问题回答得也不太好,对框架的理解还不够深入。

面试结束,面试官一脸严肃地对王铁牛说:“今天的面试就到这里,你先回家等通知吧。从面试情况来看,你对一些基础的 Java 知识掌握得还可以,但是对于 JUC、框架等方面的知识理解不够深入,后续可以加强学习。我们会综合评估后给你答复。”

以下是问题答案:

  1. Java 中基本数据类型有哪些? Java 有八种基本数据类型,分别是:
  • 整数类型:byte(1 字节,-128 到 127)、short(2 字节,-32768 到 32767)、int(4 字节,-2147483648 到 2147483647)、long(8 字节,-9223372036854775808 到 9223372036854775807)。
  • 浮点类型:float(4 字节,单精度浮点数)、double(8 字节,双精度浮点数)。
  • 字符类型:char(2 字节,用于存储单个字符,采用 Unicode 编码)。
  • 布尔类型:boolean(只有两个值,true 和 false)。
  1. ArrayList 和数组有什么区别?
  • 长度:数组的长度在创建时就固定了,后续无法改变;ArrayList 的长度是可变的,当元素数量超过当前容量时,会自动扩容。
  • 存储类型:数组可以存储基本数据类型和引用类型;ArrayList 只能存储引用类型,如果要存储基本数据类型,需要使用对应的包装类。
  • 功能:ArrayList 提供了更多的方法来操作元素,如添加、删除、查找等,使用起来更方便;数组的操作相对较少,需要手动编写代码实现一些功能。
  1. 在多线程环境下,ArrayList 是线程安全的吗? ArrayList 不是线程安全的。在多线程环境下,如果多个线程同时对 ArrayList 进行读写操作,可能会出现数据不一致或者抛出 ConcurrentModificationException 异常。例如,一个线程正在遍历 ArrayList,另一个线程同时对其进行添加或删除元素的操作,就可能导致遍历过程中出现异常。如果需要在多线程环境下使用类似的功能,可以使用 Vector 或者使用 Collections.synchronizedList 方法将 ArrayList 转换为线程安全的列表。
  2. CountDownLatch 的使用场景和原理
  • 使用场景:CountDownLatch 主要用于一个或多个线程等待其他线程完成操作后再继续执行。例如,主线程需要等待多个子线程完成数据加载后再进行汇总操作。
  • 原理:CountDownLatch 内部维护了一个计数器,在创建时需要指定计数器的初始值。当一个线程完成任务后,调用 countDown() 方法将计数器减 1。其他线程可以调用 await() 方法来等待计数器变为 0,当计数器变为 0 时,等待的线程将被唤醒继续执行。
  1. CyclicBarrier 和 CountDownLatch 有什么区别?
  • 计数器的使用方式:CountDownLatch 的计数器只能使用一次,一旦计数器变为 0,就不能再重置;CyclicBarrier 的计数器可以重复使用,当所有线程到达屏障后,计数器会重置,可以继续使用。
  • 功能侧重点:CountDownLatch 主要用于一个或多个线程等待其他线程完成任务;CyclicBarrier 主要用于多个线程之间相互等待,直到所有线程都到达某个点后再继续执行。
  • 执行动作:CyclicBarrier 可以在所有线程到达屏障时执行一个额外的任务;CountDownLatch 没有这个功能。
  1. ReentrantLock 和 synchronized 有什么不同?
  • 锁的获取和释放:synchronized 是 Java 中的关键字,是隐式锁,由 JVM 自动获取和释放锁;ReentrantLock 是一个类,需要手动调用 lock() 方法获取锁,调用 unlock() 方法释放锁。
  • 可中断性:ReentrantLock 可以被中断,当一个线程持有锁时,其他线程可以通过调用 lockInterruptibly() 方法并在等待过程中被中断;synchronized 不可以被中断。
  • 公平性:ReentrantLock 可以指定为公平锁或非公平锁,公平锁会按照线程请求锁的顺序来分配锁;synchronized 是非公平锁。
  • 锁绑定多个条件:ReentrantLock 可以通过 newCondition() 方法创建多个条件对象,实现更细粒度的线程控制;synchronized 只能使用一个隐式的条件。
  1. Spring 中的 IoC 和 AOP 是什么,能举例说明吗?
  • IoC(控制反转):是一种设计思想,将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。Spring 容器负责创建对象并注入对象之间的依赖关系。例如,在一个 Web 应用中,有一个 UserService 类需要依赖 UserDao 类来进行数据库操作。传统方式下,UserService 类需要自己创建 UserDao 对象;在 Spring 中,通过配置文件或注解,Spring 容器会创建 UserDao 对象并注入到 UserService 类中,实现了对象创建和依赖管理的反转。
  • AOP(面向切面编程):是一种编程范式,用于将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来。通过在程序运行期间动态地将这些横切关注点切入到指定的方法或类中。例如,在一个系统中,需要对所有的业务方法进行日志记录,如果在每个业务方法中都添加日志记录代码,会导致代码冗余。使用 AOP 可以定义一个日志切面,在业务方法执行前后自动记录日志。
  1. Spring Boot 相比 Spring 有什么优势?
  • 简化配置:Spring Boot 提供了大量的自动配置,减少了繁琐的 XML 配置文件或 Java 配置类。开发者只需要引入相应的依赖,Spring Boot 会自动根据依赖进行配置。
  • 快速开发:Spring Boot 内置了嵌入式服务器(如 Tomcat、Jetty 等),可以直接将应用打包成可执行的 JAR 文件,快速部署和运行。
  • 依赖管理:Spring Boot 提供了 Starter 依赖,将常用的依赖组合在一起,开发者只需要引入一个 Starter 依赖,就可以自动引入相关的依赖,避免了手动管理依赖的复杂性。
  • 监控和管理:Spring Boot Actuator 提供了丰富的监控和管理功能,如健康检查、性能指标监控等,方便开发者对应用进行管理和维护。
  1. MyBatis 中 #{} 和 ${} 的区别是什么?
  • SQL 注入风险:#{} 是预编译处理,MyBatis 会将 #{} 替换为占位符(?),可以有效防止 SQL 注入攻击;是字符串替换,MyBatis会直接将{} 是字符串替换,MyBatis 会直接将 {} 替换为传入的值,如果传入的值包含恶意 SQL 语句,可能会导致 SQL 注入。
  • 使用场景:#{} 通常用于传入参数,如查询条件、插入值等;主要用于传入表名、列名等,因为这些内容不能使用占位符。例如,当需要动态指定表名时,可以使用{} 主要用于传入表名、列名等,因为这些内容不能使用占位符。例如,当需要动态指定表名时,可以使用 {}。但使用 ${} 时要确保传入的值是安全的。