面试官:请简要介绍一下 Java 的基本数据类型和引用数据类型。
王铁牛:基本数据类型包括整数类型 byte、short、int、long,浮点类型 float、double,字符类型 char,布尔类型 boolean。引用数据类型有类、接口、数组等。
面试官:回答得不错。那说说 JVM 的内存结构主要有哪些部分?
王铁牛:JVM 内存结构主要有堆、栈、方法区、程序计数器、本地方法栈。
面试官:很好。再问一个,多线程中如何实现线程同步?
王铁牛:可以使用 synchronized 关键字或者 Lock 接口来实现线程同步。
第一轮提问结束。
面试官:请详细讲讲线程池的工作原理。
王铁牛:嗯……线程池有个核心线程池,当提交的任务数小于核心线程数时,会创建新线程执行任务。如果任务数大于核心线程数,就会把任务放到队列里。要是队列满了,并且线程数小于最大线程数,就会创建新线程处理任务。当线程数达到最大线程数后,再有新任务,就会根据拒绝策略处理。
面试官:那 HashMap 的底层数据结构是什么?
王铁牛:是数组加链表,后来链表长度超过 8 会转成红黑树。
面试官:ArrayList 是如何实现动态扩容的?
王铁牛:大概就是当容量不够时,会创建一个新的更大的数组,然后把原来数组的元素复制过去。
第二轮提问结束。
面试官:说说 Spring 的核心特性。
王铁牛:Spring 有依赖注入、面向切面编程、IoC 容器这些特性。
面试官:Spring Boot 与传统 Spring 相比有哪些优势?
王铁牛:Spring Boot 更简单,自动配置,能快速搭建项目。
面试官:MyBatis 的工作原理是什么?
王铁牛:通过 XML 或注解配置 SQL,然后利用反射等机制执行 SQL。
面试官:Dubbo 的服务调用流程是怎样的?
王铁牛:呃……就是服务提供者注册服务,消费者通过注册中心获取服务信息然后调用。
第三轮提问结束。
面试结束,面试官表示会让王铁牛回家等通知。
答案:
- Java 的基本数据类型和引用数据类型:
- 基本数据类型:
- 整数类型:
- byte:占 1 个字节,范围是 -128 到 127。主要用于存储较小的整数,比如网络协议中的一些状态码等。
- short:占 2 个字节,范围是 -32768 到 32767。在一些对内存要求较高且数据范围不大的场景中使用。
- int:占 4 个字节,范围是 -2147483648 到 2147483647,是最常用的整数类型。
- long:占 8 个字节,用于表示较大范围的整数,比如表示时间戳等。
- 浮点类型:
- float:占 4 个字节,用于表示单精度浮点数,精度相对较低,适用于对精度要求不是特别高的科学计算等场景。
- double:占 8 个字节,用于表示双精度浮点数,精度较高,常用于金融计算等对精度要求高的场景。
- 字符类型:char:占 2 个字节,用于存储单个字符,比如一个字母、一个数字或一个标点符号等。
- 布尔类型:boolean:占 1 位,只有 true 和 false 两个值,常用于逻辑判断。
- 整数类型:
- 引用数据类型:
- 类:是 Java 面向对象编程的核心,封装了数据和行为。比如自定义的 User 类来表示用户信息。
- 接口:定义了一组方法签名,实现接口的类必须实现这些方法。例如 Comparable 接口用于对象比较。
- 数组:可以存储多个相同类型的数据,比如 int[] array = new int[5]; 定义了一个长度为 5 的整数数组。
- 基本数据类型:
- JVM 的内存结构主要部分:
- 堆:是 JVM 中最大的一块内存区域,用于存储对象实例。所有的对象实例以及数组都在堆上分配内存。当对象不再被引用时,会被垃圾回收机制回收。例如,我们创建一个 User 对象 new User(),这个对象就存放在堆中。
- 栈:主要用于存储局部变量和方法调用的上下文。每个线程都有自己独立的栈。方法执行时,局部变量会在栈中分配空间。比如方法中的 int i = 10; 变量 i 就在栈中。
- 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量等数据。例如类的字节码文件加载后就在方法区。
- 程序计数器:是一块较小的内存区域,它记录着当前线程执行的字节码指令的地址。每个线程都有自己的程序计数器。
- 本地方法栈:与栈类似,用于执行本地方法(用 C 或 C++ 实现的方法)。
- 多线程中实现线程同步的方法:
- synchronized 关键字:
- 可以修饰方法,使得该方法在同一时刻只能被一个线程访问。例如:
public synchronized void method() { // 方法体 } - 也可以修饰代码块,对指定的代码块进行同步。比如:
synchronized (this) { // 同步代码块 }
- 可以修饰方法,使得该方法在同一时刻只能被一个线程访问。例如:
- Lock 接口:
- 常用的实现类有 ReentrantLock。例如:
private Lock lock = new ReentrantLock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); } - Lock 接口提供了比 synchronized 更灵活的同步控制,比如可以实现公平锁、可中断锁等。
- 常用的实现类有 ReentrantLock。例如:
- synchronized 关键字:
- 线程池的工作原理:
- 核心线程池:当提交的任务数小于核心线程数时,线程池会创建新线程来执行任务。核心线程会一直存活在线程池中,除非设置了 allowCoreThreadTimeOut 为 true。
- 任务队列:当任务数大于核心线程数时,新提交的任务会被放入任务队列中。常见的任务队列有 ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)等。
- 最大线程数:如果任务队列已满,并且线程数小于最大线程数,线程池会创建新线程来处理任务。
- 拒绝策略:当线程数达到最大线程数且任务队列已满时,再有新任务提交,就会根据拒绝策略处理。常见的拒绝策略有 AbortPolicy(抛出异常)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)、CallerRunsPolicy(由调用线程处理任务)。
- HashMap 的底层数据结构:
- HashMap 底层是数组加链表(在 JDK 1.8 后链表长度超过 8 会转成红黑树)。
- 初始时,HashMap 有一个默认大小的数组。当添加键值对时,会通过计算键的哈希值,然后对数组长度取模得到数组下标。如果该下标为空,就直接插入新节点。如果不为空,就会遍历链表(或红黑树),如果找到相同键,就更新值;如果没找到,就插入新节点。当链表长度超过 8 且数组容量大于等于 64 时,链表会转成红黑树,以提高查找效率。
- ArrayList 实现动态扩容的方式:
- 当 ArrayList 的容量不够时,会创建一个新的更大的数组。新数组的容量是原数组容量的 1.5 倍(如果原数组容量小于 64)或者原数组容量 + 原数组容量右移一位(如果原数组容量大于等于 64)。
- 然后将原来数组的元素复制到新数组中,再把新的元素添加到新数组。例如,原数组容量为 10,扩容后新数组容量为 15;原数组容量为 100,扩容后新数组容量为 150。
- Spring 的核心特性:
- 依赖注入:通过控制反转(IoC)容器,将对象之间的依赖关系由程序代码直接控制转换为由容器来管理。例如,一个 Service 类依赖于 Dao 类,通过依赖注入可以在运行时动态地将 Dao 类的实例注入到 Service 类中。
- 面向切面编程(AOP):可以在不修改原有代码的基础上,对业务逻辑进行横向切割,比如实现日志记录、事务管理等功能。例如,通过 AOP 可以在方法执行前后自动记录日志。
- IoC 容器:负责创建、配置和管理对象之间的依赖关系。它使得对象之间的依赖关系更加清晰和易于维护。比如通过 XML 配置文件或者注解配置,让 IoC 容器知道如何创建和注入对象。
- Spring Boot 与传统 Spring 相比的优势:
- 更简单:Spring Boot 采用了自动配置的机制,大大减少了开发人员的配置工作量。例如,对于数据库连接,传统 Spring 需要配置很多参数,而 Spring Boot 只需要引入相关依赖,就能自动配置好大部分数据库连接相关的设置。
- 自动配置:它能够根据项目中引入的依赖自动猜测并配置相关的组件和功能。比如引入了 Spring Data JPA 依赖,Spring Boot 就能自动配置好 JPA 的相关环境,使得开发人员可以快速开始数据访问层的开发。
- 快速搭建项目:可以通过 Spring Initializr 快速创建一个基于 Spring Boot 的项目模板,包含了项目所需的基本依赖和结构,开发人员可以在此基础上快速进行业务功能的开发。
- MyBatis 的工作原理:
- 通过 XML 或注解配置 SQL 语句。例如在 XML 中可以这样配置一个查询用户的 SQL:
<select id="getUser" parameterType="int" resultType="User"> select * from user where id = #{id} </select> - 利用反射等机制执行 SQL。MyBatis 通过解析 XML 配置文件或注解,将 SQL 语句封装成 MappedStatement 对象。在执行 SQL 时,根据传入的参数,通过反射机制创建参数对象,然后根据 SQL 语句和参数执行数据库操作,并将结果封装成相应的 Java 对象返回。比如调用 getUser 方法时,传入用户 id,MyBatis 会根据配置的 SQL 去数据库查询并返回 User 对象。
- 通过 XML 或注解配置 SQL 语句。例如在 XML 中可以这样配置一个查询用户的 SQL:
- Dubbo 的服务调用流程:
- 服务提供者注册服务:服务提供者启动时,将自己提供的服务接口、实现类以及相关配置信息注册到注册中心。例如,一个订单服务提供者会把订单服务的接口和实现类等信息注册到 Zookeeper 等注册中心。
- 消费者获取服务信息:消费者启动时,会从注册中心获取服务提供者的地址等信息。比如订单服务消费者会从注册中心获取订单服务提供者的 IP 和端口等信息。
- 消费者调用服务:消费者根据获取到的服务信息,通过远程调用协议(如 Dubbo 协议)调用服务提供者的服务。例如,订单服务消费者通过 Dubbo 协议向订单服务提供者发送请求,获取订单相关信息。在这个过程中,涉及到网络传输、序列化和反序列化等操作,以确保请求和响应数据能够正确地在服务提供者和消费者之间传递。