《互联网大厂Java面试:核心知识大考察》

37 阅读10分钟

互联网大厂Java面试:核心知识大考察

这是一场互联网大厂的Java岗位面试,面试官正对面坐着求职者王铁牛。面试正式开始。

面试官:第一轮问题来了。第一个,Java中多线程创建有几种方式? 王铁牛:有三种,继承Thread类、实现Runnable接口、还有实现Callable接口通过FutureTask获取返回值。 面试官:不错。第二个,讲讲线程池的核心参数有哪些? 王铁牛:有核心线程数、最大线程数、存活时间、时间单位,还有任务队列。 面试官:回答得很准确。第三个,HashMap在JDK1.7和JDK1.8中有什么主要区别? 王铁牛:1.8中链表转红黑树,还有1.7是头插法,1.8是尾插法。 面试官:很好,第一轮表现不错。

面试官:第二轮。第一个,Spring的IOC和AOP是什么? 王铁牛:IOC是控制反转,把对象创建和管理交给Spring容器。AOP是面向切面编程,能在不修改代码情况下增加功能。 面试官:不错。第二个,Spring Boot自动配置原理是什么? 王铁牛:嗯……就是它会根据依赖自动配置一些东西,具体怎么弄不太清楚。 面试官:好吧。第三个,MyBatis的一级缓存和二级缓存有什么区别? 王铁牛:一级缓存是SqlSession级别的,二级缓存是namespace级别的,好像二级缓存能跨SqlSession。 面试官:第二轮整体还行,有些回答可以更深入。

面试官:第三轮。第一个,Dubbo的服务暴露和引用流程是怎样的? 王铁牛:呃……就是服务提供者暴露服务,消费者引用,中间好像有注册中心啥的。 面试官:说的太笼统了。第二个,RabbitMQ的消息确认机制是怎样的? 王铁牛:就是发消息后,会有确认,保证消息不丢失,具体细节不太记得了。 面试官:好。第三个,xxl - job的调度原理是什么? 王铁牛:不太清楚,好像是定时调度任务。 面试官:最后一个,Redis的持久化方式有哪些? 王铁牛:有RDB和AOF。 面试官:王铁牛,整体来看,基础的知识点你掌握得还可以,但对于一些稍微复杂深入的技术原理,回答得不够清晰准确。今天的面试就到这里,你回家等通知吧。

答案:

  1. Java多线程创建方式
    • 继承Thread类:创建一个类继承Thread类,重写run方法,通过创建该类实例并调用start方法启动线程。例如:
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}
MyThread thread = new MyThread();
thread.start();
- **实现Runnable接口**:创建一个类实现Runnable接口,实现run方法,将该类实例作为参数传递给Thread构造函数创建线程。例如:
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
- **实现Callable接口**:创建一个类实现Callable接口,实现call方法,通过FutureTask包装该类实例,再将FutureTask作为参数传递给Thread构造函数创建线程,可通过FutureTask的get方法获取线程执行返回值。例如:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 100;
    }
}
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
    Integer result = futureTask.get();
    System.out.println("线程返回值:" + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}
  1. 线程池核心参数
    • 核心线程数(corePoolSize):线程池中一直存活的线程数,即使这些线程处于空闲状态,也不会被销毁,除非设置了allowCoreThreadTimeOut为true。
    • 最大线程数(maximumPoolSize):线程池允许创建的最大线程数。当任务队列满了且核心线程都在忙碌时,会创建新线程直到达到最大线程数。
    • 存活时间(keepAliveTime):当线程数大于核心线程数时,多余的空闲线程存活的最长时间。超过这个时间,多余线程会被销毁。
    • 时间单位(unit):存活时间的单位,如TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)等。
    • 任务队列(workQueue):用于存放等待执行的任务。常用的任务队列有ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)、SynchronousQueue(不存储任务,直接提交给线程执行)等。
  2. HashMap在JDK1.7和JDK1.8中的区别
    • 数据结构:JDK1.7中HashMap由数组 + 链表组成;JDK1.8中当链表长度大于8且数组长度大于64时,链表会转换为红黑树,数据结构变为数组 + 链表 + 红黑树,这样在查找元素时,红黑树的时间复杂度为O(logn),相比链表的O(n)大大提高了查找效率。
    • 插入数据方式:JDK1.7采用头插法,新元素会插入到链表头部;JDK1.8采用尾插法,新元素插入到链表尾部,避免了JDK1.7在多线程环境下扩容时可能出现的链表成环问题。
  3. Spring的IOC和AOP
    • IOC(控制反转):将对象的创建和管理控制权从应用程序代码转移到Spring容器中。通过依赖注入(DI),Spring容器在运行时将对象所依赖的其他对象自动注入到该对象中。例如,一个Service类依赖一个Dao类,在传统方式下需要在Service类中手动创建Dao实例,而在Spring中,通过配置或注解,Spring容器会自动创建Dao实例并注入到Service中。
    • AOP(面向切面编程):将一些与业务逻辑无关但又贯穿多个业务模块的功能(如日志记录、事务管理、权限控制等)抽取出来,形成一个独立的切面,在不修改原有业务逻辑代码的情况下,将这些切面功能动态地织入到目标业务模块中。例如,在方法执行前后记录日志,通过AOP可以在不修改方法代码的情况下实现。
  4. Spring Boot自动配置原理:Spring Boot通过@EnableAutoConfiguration注解开启自动配置功能。它会根据classpath下存在的依赖,在META - INF/spring.factories文件中查找对应的自动配置类。这些自动配置类会根据条件(如是否存在某个Bean、是否有特定的配置属性等)来决定是否生效。例如,如果classpath下有Tomcat依赖,Spring Boot会自动配置Tomcat作为Web服务器;如果有MyBatis依赖,会自动配置MyBatis相关的数据源、SqlSessionFactory等。
  5. MyBatis的一级缓存和二级缓存
    • 一级缓存:是SqlSession级别的缓存,在同一个SqlSession中,执行相同的SQL查询时,MyBatis会先从一级缓存中查找数据,如果有则直接返回,不会再去数据库查询。一级缓存默认开启,其生命周期与SqlSession相同,当SqlSession执行commit、rollback操作或关闭时,一级缓存会被清空。
    • 二级缓存:是namespace级别的缓存,多个SqlSession可以共享二级缓存。当一个SqlSession查询数据时,先从一级缓存找,找不到再从二级缓存找,还找不到才去数据库查询。二级缓存需要手动开启,在mapper.xml文件中配置标签。二级缓存的生命周期比一级缓存长,它会在应用程序运行期间一直存在,除非手动清除。
  6. Dubbo的服务暴露和引用流程
    • 服务暴露流程
      • 配置阶段:服务提供者在Spring配置文件或通过注解配置要暴露的服务接口、实现类、协议、端口等信息。
      • 初始化阶段:Dubbo框架根据配置信息初始化服务实例,创建服务暴露的相关对象,如Protocol、Exporter等。
      • 注册阶段:服务提供者通过RegistryFactory获取注册中心实例,将服务接口和地址等信息注册到注册中心(如Zookeeper)。
      • 监听阶段:服务提供者启动网络监听,等待消费者调用。
    • 服务引用流程
      • 配置阶段:服务消费者在Spring配置文件或通过注解配置要引用的服务接口、协议等信息。
      • 订阅阶段:服务消费者通过RegistryFactory获取注册中心实例,向注册中心订阅所需服务的地址列表。
      • 获取服务阶段:注册中心将服务提供者地址列表返回给消费者,消费者根据负载均衡策略选择一个服务提供者地址。
      • 调用阶段:消费者通过ProxyFactory创建服务代理对象,通过网络通信(如Dubbo协议)调用服务提供者的方法。
  7. RabbitMQ的消息确认机制
    • 生产者确认(publisher confirm):生产者将消息发送到RabbitMQ后,RabbitMQ会给生产者发送确认消息,告知消息是否成功接收。生产者通过调用channel.confirmSelect()方法开启确认模式,然后可以通过addConfirmListener()方法添加监听器,在监听器中处理确认结果。如果消息成功到达RabbitMQ,会收到ACK确认;如果消息未成功到达,会收到NACK确认,生产者可以根据情况进行重发等处理。
    • 消费者确认(consumer ack):消费者从RabbitMQ获取消息并处理完成后,需要向RabbitMQ发送确认消息,告知RabbitMQ可以将该消息从队列中删除。默认情况下,RabbitMQ在消息发送给消费者后就会从队列中删除,但这样可能导致消费者处理消息失败时消息丢失。通过设置autoAck为false,消费者手动调用channel.basicAck()方法发送确认消息,只有收到确认消息后,RabbitMQ才会从队列中删除消息,保证消息不丢失。
  8. xxl - job的调度原理
    • 调度中心:是xxl - job的核心组件,负责管理调度任务,包括任务的新增、修改、删除,以及触发任务调度。调度中心基于Quartz实现定时任务调度,通过数据库存储任务信息和调度日志。
    • 执行器:部署在业务系统中,负责接收调度中心发送的任务并执行。执行器启动时会向调度中心注册自己,调度中心根据任务配置,在指定时间将任务发送给对应的执行器。执行器接收到任务后,通过反射调用任务对应的方法执行任务,并将执行结果返回给调度中心。
  9. Redis的持久化方式
    • RDB(Redis Database):在指定的时间间隔内将内存中的数据集快照写入磁盘,生成一个dump.rdb文件。优点是恢复速度快,因为是直接将快照文件读入内存;缺点是可能会丢失最后一次快照到Redis崩溃期间的数据,因为快照是定期进行的。可以通过配置文件设置save参数来指定快照的触发条件,如save 900 1表示900秒内如果有1个键被修改则进行快照。
    • AOF(Append - Only - File):以日志的形式记录Redis服务器执行的写操作命令,在Redis重启时,通过重新执行AOF文件中的命令来恢复数据。优点是数据完整性好,基本能保证不丢失数据;缺点是AOF文件会不断增大,需要定期进行重写(rewrite)操作,以压缩文件大小。可以通过配置文件开启AOF持久化,设置appendonly yes,还可以设置appendfsync参数来控制AOF文件的写入频率,如always表示每次写操作都同步到AOF文件,everysec表示每秒同步一次,no表示由操作系统决定何时同步。