《互联网大厂 Java 面试:核心知识、框架与中间件大考验》
王铁牛怀揣着对互联网大厂的憧憬,坐在了面试官面前,一场紧张的 Java 技术面试就此拉开帷幕。
第一轮面试开始 面试官:“首先问你几个 Java 核心知识的问题。Java 中基本数据类型有哪些?” 王铁牛:“有 byte、short、int、long、float、double、char、boolean。” 面试官:“回答正确,基础很扎实。那在 Java 里,重载和重写的区别是什么?” 王铁牛:“重载是在一个类中,方法名相同但参数列表不同;重写是子类对父类中允许访问的方法进行重新实现,方法名、参数列表和返回类型都一样。” 面试官:“非常棒。那 Java 的访问修饰符有哪些,分别有什么作用?” 王铁牛:“有 public、protected、default(无修饰符)和 private。public 表示全局可见,所有类都能访问;protected 表示同一个包内的类和不同包的子类可以访问;default 表示同一个包内的类可以访问;private 表示只有本类可以访问。” 面试官:“回答得很准确。看来你对 Java 核心知识掌握得不错。接下来进入第二轮。”
第二轮面试开始 面试官:“我们来聊聊 JUC 和多线程相关的内容。什么是线程池,为什么要使用线程池?” 王铁牛:“线程池就是预先创建好一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务。使用线程池可以减少线程创建和销毁的开销,提高系统性能,还能控制并发线程的数量,避免资源过度使用。” 面试官:“回答得很好。那线程池有哪些重要的参数?” 王铁牛:“有 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程空闲存活时间)、unit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)和 handler(任务拒绝策略)。” 面试官:“很不错。那在多线程环境下,如何保证数据的安全性?” 王铁牛:“可以使用 synchronized 关键字或者 Lock 接口来实现同步,还可以使用原子类,比如 AtomicInteger 等。” 面试官:“回答得很全面。现在进入第三轮。”
第三轮面试开始 面试官:“我们来谈谈一些框架和中间件。Spring 框架的核心特性有哪些?” 王铁牛:“Spring 框架的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 是将对象的创建和依赖关系的管理交给 Spring 容器,AOP 是在不修改原有代码的基础上,对程序进行增强。” 面试官:“回答正确。那 Spring Boot 和 Spring 有什么区别?” 王铁牛:“Spring Boot 是基于 Spring 构建的,它简化了 Spring 应用的开发,提供了自动配置的功能,减少了大量的配置文件,让开发人员可以更快速地搭建项目。” 面试官:“很好。那 MyBatis 是如何实现数据库操作的?” 王铁牛:“MyBatis 通过 XML 配置文件或者注解来定义 SQL 语句,然后通过 SqlSession 来执行这些 SQL 语句,将查询结果映射到 Java 对象中。” 面试官:“看来你对框架也有一定的了解。不过,我再问你一个稍微复杂点的问题,Dubbo 是如何进行服务注册和发现的?” 王铁牛:“嗯……那个,好像是通过一个什么中心来注册服务,然后发现的时候就去那里找,具体的我有点记不清了。” 面试官:“这个问题回答得不太清晰。还有 RabbitMq 如何保证消息的可靠性?” 王铁牛:“这个……我记得有确认机制,具体怎么实现我不太确定了。” 面试官:“看来你对这部分知识掌握得不够扎实。最后一个问题,xxl - job 是如何实现分布式任务调度的?” 王铁牛:“我只知道它能做分布式任务调度,具体原理不太清楚。”
面试结束 面试官扶了扶眼镜,严肃地说:“王铁牛,从整体面试情况来看,你对 Java 核心知识、线程池、Spring 等基础部分掌握得还可以,回答得比较准确,说明你有一定的基础。但在一些框架和中间件的深入原理方面,比如 Dubbo 的服务注册发现、RabbitMq 消息可靠性保证以及 xxl - job 的分布式任务调度原理,你回答得不太理想,暴露出你对这些知识的掌握不够深入。我们需要综合考虑所有面试者的情况,你先回家等通知吧。”
问题答案详解
- Java 中基本数据类型有哪些?
- Java 中有 8 种基本数据类型,可分为 4 类:
- 整数类型:byte(1 字节,范围 -128 到 127)、short(2 字节,范围 -32768 到 32767)、int(4 字节,范围 -2147483648 到 2147483647)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节),用于表示单个字符。
- 布尔类型:boolean,只有 true 和 false 两个值。
- Java 中有 8 种基本数据类型,可分为 4 类:
- 在 Java 里,重载和重写的区别是什么?
- 重载(Overloading):发生在同一个类中,方法名相同但参数列表不同(参数的类型、个数、顺序不同),与返回类型和访问修饰符无关。重载是编译时多态的体现,编译器根据调用方法时传入的参数来决定调用哪个重载方法。
- 重写(Overriding):发生在子类和父类之间,子类重写父类中允许访问的方法,方法名、参数列表和返回类型必须和父类被重写的方法一致(Java 5 及以后,返回类型可以是父类方法返回类型的子类,称为协变返回类型)。重写是运行时多态的体现,根据对象的实际类型来决定调用哪个重写方法。
- Java 的访问修饰符有哪些,分别有什么作用?
- public:具有最大的访问权限,被 public 修饰的类、方法、属性可以被任何类访问。
- protected:同一个包内的类可以访问,不同包的子类也可以访问。
- default(无修饰符):也称为包访问权限,只有同一个包内的类可以访问。
- private:具有最小的访问权限,只有本类内部可以访问被 private 修饰的方法和属性。
- 什么是线程池,为什么要使用线程池?
- 线程池:是一种线程使用模式,预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完毕后线程不会销毁,而是返回线程池等待下一个任务。
- 使用线程池的原因:
- 减少线程创建和销毁的开销:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,提高系统性能。
- 提高响应速度:任务提交时可以直接从线程池中获取线程执行,无需等待线程创建。
- 控制并发线程数量:可以避免创建过多线程导致系统资源耗尽,提高系统的稳定性。
- 线程池有哪些重要的参数?
- corePoolSize:核心线程数,线程池在初始化时会创建的线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。当任务队列满了,并且线程数达到核心线程数时,线程池会创建新的线程,直到达到最大线程数。
- keepAliveTime:线程空闲存活时间,当线程空闲时间超过这个时间,并且线程数大于核心线程数时,多余的线程会被销毁。
- unit:keepAliveTime 的时间单位,如 TimeUnit.SECONDS 表示秒。
- workQueue:任务队列,用于存储提交但尚未执行的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
- threadFactory:线程工厂,用于创建线程,可以自定义线程的名称、优先级等属性。
- handler:任务拒绝策略,当任务队列满了,并且线程数达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程执行任务)等。
- 在多线程环境下,如何保证数据的安全性?
- 使用 synchronized 关键字:可以修饰方法或代码块,当一个线程访问被 synchronized 修饰的方法或代码块时,会获取对象的锁,其他线程需要等待该线程释放锁才能访问。
- 使用 Lock 接口:如 ReentrantLock,它提供了比 synchronized 更灵活的锁机制,可以实现公平锁、可重入锁等。使用时需要手动加锁和解锁。
- 使用原子类:如 AtomicInteger、AtomicLong 等,这些类使用了 CAS(Compare - And - Swap)算法,在多线程环境下可以保证对变量的原子操作,避免了使用锁带来的性能开销。
- Spring 框架的核心特性有哪些?
- IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象自己来创建和管理依赖。通过 IoC,降低了对象之间的耦合度,提高了代码的可维护性和可测试性。
- AOP(面向切面编程):是在不修改原有代码的基础上,对程序进行增强。AOP 可以将一些通用的功能(如日志记录、事务管理等)从业务逻辑中分离出来,形成独立的切面,然后在合适的时机将这些切面织入到业务逻辑中。
- Spring Boot 和 Spring 有什么区别?
- Spring:是一个功能强大的 Java 开发框架,提供了 IoC、AOP 等核心特性,但配置比较繁琐,需要编写大量的 XML 配置文件或 Java 配置类。
- Spring Boot:是基于 Spring 构建的,它简化了 Spring 应用的开发。Spring Boot 提供了自动配置的功能,根据项目的依赖和配置,自动为应用配置合适的 Bean,减少了大量的配置文件。同时,Spring Boot 还提供了嵌入式服务器,方便快速部署和运行应用。
- MyBatis 是如何实现数据库操作的?
- 定义 SQL 语句:可以通过 XML 配置文件或者注解来定义 SQL 语句。在 XML 配置文件中,可以使用
<select>、<insert>、<update>、<delete>等标签来定义 SQL 语句;使用注解时,可以使用@Select、@Insert、@Update、@Delete等注解。 - 创建 SqlSession:SqlSession 是 MyBatis 执行 SQL 语句的核心对象,通过 SqlSessionFactory 来创建。SqlSessionFactory 可以通过 XML 配置文件或者 Java 代码来创建。
- 执行 SQL 语句:通过 SqlSession 对象调用相应的方法来执行 SQL 语句,如 selectOne()、selectList()、insert()、update()、delete() 等。
- 结果映射:MyBatis 会将查询结果映射到 Java 对象中,可以通过
<resultMap>标签或者注解来定义映射规则。
- 定义 SQL 语句:可以通过 XML 配置文件或者注解来定义 SQL 语句。在 XML 配置文件中,可以使用
- Dubbo 是如何进行服务注册和发现的?
- 服务注册:服务提供者在启动时,会将自己的服务信息(如服务接口名、服务地址等)注册到注册中心。Dubbo 支持多种注册中心,如 Zookeeper、Redis 等。服务提供者通过向注册中心发送注册请求,将服务信息存储在注册中心的指定节点下。
- 服务发现:服务消费者在启动时,会从注册中心获取服务提供者的信息。服务消费者会订阅注册中心上的服务节点,当服务提供者的信息发生变化时,注册中心会通知服务消费者。服务消费者根据获取到的服务提供者信息,选择合适的服务提供者进行调用。
- RabbitMq 如何保证消息的可靠性?
- 生产者确认机制:RabbitMQ 提供了两种生产者确认模式,事务模式和确认模式。在事务模式下,生产者通过开启事务来确保消息的发送成功,如果消息发送失败,事务会回滚;在确认模式下,生产者发送消息后,会等待 RabbitMQ 服务器返回确认信息,如果在一定时间内没有收到确认信息,生产者可以进行重试。
- 消息持久化:可以将消息队列、消息和交换器都设置为持久化。当 RabbitMQ 服务器重启时,持久化的消息队列、消息和交换器会被保留,避免消息丢失。
- 消费者确认机制:消费者在接收到消息后,需要向 RabbitMQ 服务器发送确认信息。如果消费者在处理消息时出现异常,没有发送确认信息,RabbitMQ 会将消息重新分发给其他消费者。
- xxl - job 是如何实现分布式任务调度的?
- 架构组成:xxl - job 由调度中心和执行器两部分组成。调度中心负责任务的管理和调度,执行器负责执行具体的任务。
- 任务注册:执行器在启动时,会向调度中心注册自己的信息,包括执行器名称、地址等。调度中心会将执行器信息存储在数据库中。
- 任务调度:调度中心根据任务的配置信息(如执行时间、执行周期等),生成任务调度计划。当任务到达执行时间时,调度中心会向相应的执行器发送任务执行请求。
- 任务执行:执行器接收到任务执行请求后,会根据任务的类型和配置信息,执行具体的任务。任务执行完成后,执行器会将执行结果返回给调度中心。
- 故障转移:如果某个执行器出现故障,调度中心会将任务重新分配给其他可用的执行器,保证任务的正常执行。