第一轮面试 面试官:先从基础的 Java 核心知识问起。Java 中基本数据类型有哪些? 王铁牛:基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那自动装箱和拆箱是怎么回事? 王铁牛:自动装箱就是把基本数据类型转为包装类型,拆箱就是反过来,把包装类型转为基本数据类型。 面试官:回答得挺好。ArrayList 和 LinkedList 在性能上有什么区别? 王铁牛:ArrayList 适合随机访问,因为它是基于数组的;LinkedList 适合插入和删除操作,因为它是链表结构。
第二轮面试 面试官:接下来聊聊 JUC 和多线程。什么是线程安全? 王铁牛:线程安全就是多个线程访问同一个资源时,不会出现数据不一致等问题。 面试官:那线程池有什么作用? 王铁牛:线程池可以复用线程,减少线程创建和销毁的开销,提高性能。 面试官:那常见的线程池类型有哪些,它们有什么特点? 王铁牛:嗯……有 FixedThreadPool,它固定线程数量;还有 CachedThreadPool,好像是按需创建线程。其他的……我有点记不太清了。
第三轮面试 面试官:最后考察下框架相关知识。Spring 框架中 IOC 和 AOP 是什么? 王铁牛:IOC 是控制反转,把对象创建和管理交给 Spring 容器;AOP 是面向切面编程,能实现一些通用功能的统一处理。 面试官:Spring Boot 相对于 Spring 有什么优势? 王铁牛:Spring Boot 能快速搭建项目,它有很多默认配置,简化了开发。 面试官:Dubbo 主要解决什么问题? 王铁牛:Dubbo 好像是做分布式服务治理的,能实现服务的注册、发现这些。具体细节我不太清楚了。
面试官:好的,今天的面试就到这里。你回去等通知吧,我们会综合评估所有候选人后,再决定是否录用你。感谢你今天来参加面试。
问题答案:
- Java 基本数据类型:Java 中有 8 种基本数据类型,分为四类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(1 位,具体实现因虚拟机而异)。这些基本数据类型是 Java 语言的基础,它们在内存占用和表示范围上各有不同,适用于不同的场景。例如,byte 类型适用于节省内存空间且数值范围较小的场景,而 double 类型适用于需要高精度浮点数运算的场景。
- 自动装箱和拆箱:自动装箱是 Java 编译器在编译期自动将基本数据类型转换为对应的包装类型。例如,Integer i = 10; 这里 10 是 int 类型,编译器会自动调用 Integer.valueOf(10) 将其装箱为 Integer 对象。自动拆箱则相反,是将包装类型转换为基本数据类型。例如,int j = i; 编译器会自动调用 i.intValue() 进行拆箱。这一机制使得基本数据类型和包装类型在使用上更加便捷,开发者无需手动进行转换,但在某些场景下可能会带来性能问题和空指针异常风险,需要注意。
- ArrayList 和 LinkedList 性能区别:ArrayList 基于数组实现,它支持快速随机访问,因为可以通过数组下标直接定位元素,时间复杂度为 O(1)。但在进行插入和删除操作时,尤其是在数组中间位置,需要移动大量元素,时间复杂度为 O(n)。LinkedList 基于链表实现,插入和删除操作只需修改链表节点的指针,时间复杂度为 O(1),但随机访问时需要从头遍历链表,时间复杂度为 O(n)。所以,如果应用场景以随机访问为主,应优先选择 ArrayList;如果以频繁插入和删除操作为主,则应选择 LinkedList。
- 线程安全:线程安全是指当多个线程访问一个类或对象时,在类或对象的设计和实现上,能够保证在任何情况下,该类或对象的状态都是正确的,不会出现数据不一致、竞争条件等问题。例如,多个线程同时对一个共享变量进行读写操作,如果没有适当的同步机制,可能会导致数据错误。实现线程安全的常见方式有使用 synchronized 关键字、Lock 接口等进行同步控制,以及使用线程安全的类如 ConcurrentHashMap 等。
- 线程池作用:线程池是一种管理和复用线程的机制。它的主要作用有:减少线程创建和销毁的开销,因为线程的创建和销毁是比较消耗系统资源的操作,线程池中的线程可以复用;提高系统响应速度,当有任务到来时,无需等待线程创建,可以直接从线程池中获取线程执行任务;便于对线程进行统一管理,如设置线程的数量上限、任务队列大小等,避免过多线程导致系统资源耗尽。
- 常见线程池类型及特点:
- FixedThreadPool:固定线程数量的线程池。它创建时会指定线程数量,线程池中的线程数量始终保持不变。当有任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列等待,直到有线程空闲。这种线程池适用于处理稳定的、数量已知的任务流,能有效控制并发线程数量,避免过多线程导致系统资源耗尽。
- CachedThreadPool:可缓存线程池。它没有固定的线程数量,线程池中的线程数量会根据任务数量动态调整。当有任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则会创建新的线程来执行任务。如果某个线程在 60 秒内没有执行任务,会被回收。这种线程池适用于处理大量短时间任务,能快速响应任务请求,但如果任务持续不断且数量过多,可能会创建大量线程,导致系统资源耗尽。
- SingleThreadExecutor:单线程线程池。它只有一个线程来执行任务,所有任务会按照提交顺序依次执行。这种线程池适用于需要顺序执行任务,且不希望有并发干扰的场景,保证了任务执行的顺序性和线程安全。
- ScheduledThreadPool:定时线程池。它可以在指定延迟后执行任务,或者定期执行任务。适用于需要定时执行任务的场景,如定时数据备份、定时任务调度等。
- Spring 中 IOC 和 AOP:
- IOC(控制反转):IOC 是 Spring 框架的核心概念之一。传统的应用程序中,对象的创建和管理由应用程序自身负责,这使得对象之间的耦合度较高。而在 Spring 中,IOC 把对象的创建和管理交给 Spring 容器,应用程序只需要使用对象,而不需要关心对象是如何创建和管理的。例如,在一个业务类中需要使用另一个服务类,传统方式是在业务类中自己创建服务类的实例,而在 Spring 中,可以通过配置或注解的方式让 Spring 容器创建并注入服务类的实例到业务类中。这样可以降低对象之间的耦合度,提高代码的可维护性和可测试性。
- AOP(面向切面编程):AOP 是一种编程范式,它将一些通用的功能(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,形成一个个切面。这些切面可以在不修改原有业务逻辑代码的情况下,在特定的连接点(如方法调用前后、异常抛出时等)插入通用功能。例如,通过 AOP 可以在所有业务方法执行前记录日志,而不需要在每个业务方法中都编写日志记录代码。AOP 提高了代码的复用性和可维护性,使得业务逻辑更加清晰。
- Spring Boot 相对 Spring 的优势:
- 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,通过简单的配置就能快速搭建一个完整的项目,无需像 Spring 那样进行繁琐的配置。例如,要搭建一个 Web 项目,在 Spring Boot 中只需要引入 spring - boot - starter - web 依赖,就自动配置好了 Web 开发所需的各种组件,包括 Tomcat 服务器、Spring MVC 等。
- 简化配置:Spring Boot 有很多默认配置,它会根据项目的依赖自动配置合理的参数。开发者只需要在 application.properties 或 application.yml 文件中进行少量的自定义配置即可。相比之下,Spring 需要手动配置大量的 XML 文件或 Java 配置类,配置过程较为复杂。
- 内置服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,无需额外部署服务器,直接运行项目的 main 方法即可启动应用,方便开发和测试。
- 生产级监控:Spring Boot Actuator 提供了生产级别的监控功能,如查看应用的健康状态、性能指标等,方便运维和管理。
- Dubbo 主要解决的问题:Dubbo 是一款高性能的 Java 分布式服务框架,主要解决以下问题:
- 服务治理:在分布式系统中,服务数量众多,需要对服务进行有效的管理。Dubbo 提供了服务注册与发现功能,服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务地址,实现了服务的自动发现和动态配置。这样当服务提供者的地址发生变化时,服务消费者无需手动修改配置,提高了系统的可维护性和扩展性。
- 负载均衡:当有多个服务提供者提供相同的服务时,Dubbo 可以实现负载均衡,将请求均匀分配到各个服务提供者上,避免某个服务提供者负载过高,提高系统的整体性能和可用性。常见的负载均衡策略有随机、轮询、最少活跃调用数等。
- 远程调用:Dubbo 支持多种远程调用协议,如 Dubbo 协议、HTTP 协议等,使得不同服务之间可以进行高效的远程通信。它对远程调用进行了封装,开发者可以像调用本地方法一样调用远程服务,降低了分布式系统开发的难度。