互联网大厂面试:Java核心、并发、框架与中间件知识大考验
在互联网大厂的一间明亮的面试室内,严肃的面试官坐在桌前,对面坐着略显紧张的求职者王铁牛。面试开始了,一场对Java核心知识的考验拉开帷幕。
第一轮面试 面试官:首先问你几个基础的问题。Java中基本数据类型有哪些? 王铁牛:这个我知道,有byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那你说说ArrayList和LinkedList的区别是什么? 王铁牛:ArrayList是基于数组实现的,它的随机访问速度快,但是插入和删除操作效率低;LinkedList是基于链表实现的,插入和删除操作效率高,随机访问速度慢。 面试官:很好,看来你对基础掌握得挺扎实。那HashMap的底层数据结构是什么? 王铁牛:HashMap底层是数组 + 链表 + 红黑树,当链表长度大于8且数组长度大于64时,链表会转化为红黑树,这样可以提高查询效率。 面试官:非常棒,回答很准确。
第二轮面试 面试官:接下来问你一些多线程和JUC相关的问题。什么是线程池?为什么要使用线程池? 王铁牛:线程池就是预先创建好一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务。使用线程池可以减少线程创建和销毁的开销,提高系统性能,还能控制并发线程的数量,避免资源耗尽。 面试官:回答得很清晰。那线程池有哪些常用的创建方式? 王铁牛:可以通过Executors工具类创建,比如newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor等。不过阿里巴巴开发手册不建议使用Executors创建线程池,推荐使用ThreadPoolExecutor自定义线程池。 面试官:没错,很有安全意识。那说说ReentrantLock和synchronized的区别。 王铁牛:嗯……它们都能实现线程同步,synchronized是Java的关键字,是隐式锁,由JVM实现加锁和解锁;ReentrantLock是Java类,是显式锁,需要手动加锁和解锁。好像就这些吧。 面试官:大体回答对了,不过还可以再深入一些。
第三轮面试 面试官:现在问你一些框架和中间件的问题。Spring的核心特性有哪些? 王铁牛:Spring的核心特性有IoC(控制反转)和AOP(面向切面编程)。IoC就是把对象的创建和依赖关系的管理交给Spring容器,AOP可以在不修改原有代码的基础上增加额外的功能。 面试官:不错。那Spring Boot和Spring的关系是什么? 王铁牛:Spring Boot是基于Spring的,它简化了Spring应用的开发,提供了自动配置等功能,让开发者可以更快速地搭建Spring应用。 面试官:回答正确。那MyBatis的一级缓存和二级缓存是怎么回事? 王铁牛:这个……一级缓存是SqlSession级别的缓存,同一个SqlSession执行相同的查询语句时会从缓存中取数据;二级缓存是Mapper级别的缓存,多个SqlSession可以共享。具体怎么实现的我有点不太清楚了。 面试官:有一定的了解,但是不够深入。
面试接近尾声,面试官放下手中的笔,看着王铁牛说:“今天的面试就到这里,你对一些基础的知识掌握得还可以,但是对于一些复杂的问题,回答得还不够深入和准确。我们会综合考虑这次面试情况,你回家等通知吧。”
问题答案
-
Java中基本数据类型有哪些? Java中的基本数据类型分为四类八种:
- 整数类型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)。
- 浮点类型:float(4字节)、double(8字节)。
- 字符类型:char(2字节)。
- 布尔类型:boolean(1位)。
-
ArrayList和LinkedList的区别是什么?
- 数据结构:ArrayList是基于动态数组实现的,LinkedList是基于双向链表实现的。
- 随机访问:ArrayList支持高效的随机访问,时间复杂度为O(1);LinkedList随机访问效率低,时间复杂度为O(n)。
- 插入和删除:ArrayList在中间插入和删除元素时,需要移动后续元素,时间复杂度为O(n);LinkedList插入和删除元素只需要修改指针,时间复杂度为O(1),但如果是指定位置插入,需要先遍历到该位置,整体效率不一定高。
- 内存占用:ArrayList的空间浪费主要体现在数组末尾预留的空间;LinkedList的空间浪费主要体现在每个节点需要额外的指针域。
-
HashMap的底层数据结构是什么? HashMap的底层数据结构是数组 + 链表 + 红黑树。
- 数组:也称为哈希桶,用于存储链表或红黑树的头节点。通过哈希函数计算键的哈希值,然后根据哈希值找到对应的数组位置。
- 链表:当多个键的哈希值冲突时,会以链表的形式存储在同一个数组位置。在JDK 8之前,链表是HashMap解决哈希冲突的主要方式。
- 红黑树:当链表长度大于8且数组长度大于64时,链表会转化为红黑树。红黑树是一种自平衡的二叉搜索树,在节点较多时可以将查找、插入和删除操作的时间复杂度从O(n)降低到O(log n)。
-
什么是线程池?为什么要使用线程池?
- 线程池定义:线程池是一种线程使用模式,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完毕后线程不会销毁,而是返回线程池等待下一个任务。
- 使用线程池的原因:
- 减少线程创建和销毁的开销:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,提高系统性能。
- 提高响应速度:任务提交时可以直接从线程池中获取线程执行,无需等待线程创建。
- 控制并发线程数量:可以避免创建过多线程导致系统资源耗尽,从而提高系统的稳定性。
- 方便线程管理:可以对线程进行统一的监控和管理。
-
线程池有哪些常用的创建方式?
- 通过Executors工具类创建:
- newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入队列等待。
- newCachedThreadPool:创建一个可缓存的线程池,线程数量不固定,如果线程池中有空闲线程,则使用空闲线程执行任务;如果没有空闲线程,则创建新线程执行任务。当线程空闲时间过长时,会被自动回收。
- newSingleThreadExecutor:创建一个单线程的线程池,线程池中只有一个线程,所有任务按照顺序依次执行。
- 使用ThreadPoolExecutor自定义线程池:
ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, // 核心线程数 maximumPoolSize, // 最大线程数 keepAliveTime, // 线程空闲时间 TimeUnit unit, // 时间单位 workQueue, // 任务队列 threadFactory, // 线程工厂 handler // 拒绝策略 );
阿里巴巴开发手册不建议使用Executors创建线程池,因为Executors创建的线程池可能会导致OOM(内存溢出)问题,推荐使用ThreadPoolExecutor自定义线程池,明确线程池的参数,避免潜在的风险。
- 通过Executors工具类创建:
-
ReentrantLock和synchronized的区别
- 实现层面:synchronized是Java的关键字,是由JVM实现的,底层通过对象头的Mark Word来实现加锁和解锁;ReentrantLock是Java类,是基于AQS(AbstractQueuedSynchronizer)实现的。
- 锁的获取和释放:synchronized是隐式锁,当进入同步代码块或方法时自动加锁,退出时自动解锁;ReentrantLock是显式锁,需要手动调用lock()方法加锁,unlock()方法解锁,通常在finally块中释放锁,以确保锁一定会被释放。
- 锁的特性:
- 可重入性:synchronized和ReentrantLock都具有可重入性,即同一个线程可以多次获取同一把锁。
- 可中断性:synchronized不可中断,当一个线程获取锁失败进入阻塞状态后,只能等待持有锁的线程释放锁;ReentrantLock可以通过lockInterruptibly()方法实现可中断锁,在等待锁的过程中可以被其他线程中断。
- 公平性:synchronized是非公平锁,多个线程竞争锁时,不一定按照请求锁的顺序获取锁;ReentrantLock可以通过构造函数指定是否为公平锁,公平锁会按照请求锁的顺序依次获取锁。
- 锁的粒度:synchronized的锁粒度通常较大,只能锁定一个代码块或方法;ReentrantLock可以实现更细粒度的锁控制,例如可以使用多个Condition对象实现分组唤醒等待线程。
-
Spring的核心特性有哪些?
- IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到Spring容器中。在传统的编程方式中,对象的创建和依赖关系的管理由开发者手动完成,而在Spring中,对象的创建和依赖关系的注入由Spring容器负责。通过IoC,降低了代码的耦合度,提高了代码的可维护性和可测试性。
- AOP(面向切面编程):是一种编程范式,它允许开发者在不修改原有代码的基础上,对程序进行增强。AOP将程序中的一些通用功能(如日志记录、事务管理等)从业务逻辑中分离出来,形成独立的切面,然后在合适的时机将这些切面织入到业务逻辑中。AOP可以提高代码的复用性和可维护性。
-
Spring Boot和Spring的关系是什么? Spring Boot是基于Spring的,它是为了简化Spring应用的开发而诞生的。
- 简化配置:Spring Boot提供了自动配置功能,它会根据项目中引入的依赖自动配置Spring应用,减少了大量的XML配置和Java配置代码。开发者只需要添加必要的依赖和少量的配置,就可以快速搭建一个Spring应用。
- 快速开发:Spring Boot内置了嵌入式服务器(如Tomcat、Jetty等),可以直接将应用打包成可执行的JAR文件,通过java -jar命令运行,无需额外的服务器配置和部署。
- 约定优于配置:Spring Boot遵循约定优于配置的原则,它有一套默认的配置约定,开发者只需要按照约定进行开发,不需要进行大量的配置。如果需要自定义配置,也可以很方便地进行修改。
-
MyBatis的一级缓存和二级缓存是怎么回事?
- 一级缓存:是SqlSession级别的缓存,同一个SqlSession中执行相同的查询语句时,会先从一级缓存中查找,如果缓存中存在,则直接返回缓存中的数据,无需再次查询数据库。一级缓存是默认开启的,不能关闭。当执行增删改操作时,会清空一级缓存。
- 二级缓存:是Mapper级别的缓存,多个SqlSession可以共享同一个Mapper的二级缓存。二级缓存需要手动开启,在Mapper.xml文件中添加
<cache/>标签或在Mapper接口上添加@CacheNamespace注解。当执行增删改操作时,会清空对应的二级缓存。二级缓存可以提高系统的性能,特别是在多用户、高并发的场景下。