第一轮面试 面试官:先问些基础的,Java 中 ArrayList 和 LinkedList 的区别是什么? 王铁牛:ArrayList 是基于数组实现的,随机访问快,LinkedList 是基于链表实现的,插入删除快。 面试官:不错,回答得很准确。那 HashMap 在 JDK1.7 和 JDK1.8 中有什么主要区别? 王铁牛:1.8 好像是用了红黑树,1.7 是链表,其他的……不太记得了。 面试官:嗯,方向对了。最后一个基础问题,Spring 中 IOC 和 AOP 分别是什么? 王铁牛:IOC 是控制反转,把对象创建和管理交给 Spring 容器;AOP 是面向切面编程,能在不修改代码的情况下增加功能。 面试官:回答得不错,基础掌握得还行。
第二轮面试 面试官:进入第二轮,谈谈多线程中线程池的核心参数有哪些,分别有什么作用? 王铁牛:有核心线程数,最大线程数……还有个队列,作用嘛,核心线程数就是一开始的线程数量,最大线程数就是最多能有多少线程,队列就是放任务的。 面试官:嗯,大概意思有了。那 JUC 包下的 CountDownLatch 是做什么的? 王铁牛:好像是用来控制线程等待的,等一些条件满足了再继续执行。 面试官:能说具体点怎么用吗? 王铁牛:呃……就是创建一个 CountDownLatch 对象,设置一个初始值,线程调用 await 方法等待,其他线程完成任务后调用 countDown 方法减少计数。 面试官:好,最后问下 MyBatis 中 #{} 和 {} 的区别。 **王铁牛**:#{} 是预编译,能防止 SQL 注入,{} 是直接替换,可能有 SQL 注入风险。 面试官:回答得还可以,对这些知识有一定了解。
第三轮面试 面试官:现在是第三轮,Dubbo 框架在分布式系统中有什么作用,它的工作原理是什么? 王铁牛:Dubbo 是做服务治理的,能让服务之间相互调用。原理嘛,好像是有注册中心,服务提供者注册服务,消费者去注册中心找服务。 面试官:那 RabbitMQ 在项目中一般用于什么场景,讲讲它的消息模型。 王铁牛:一般用于异步处理、解耦这些场景。消息模型……有 direct、topic 这些,direct 是根据路由键直接匹配,topic 是按规则匹配。 面试官:最后问下 xxl - job 这个分布式任务调度框架,它的核心功能和优势是什么? 王铁牛:核心功能就是能调度任务,优势嘛,好像是比较简单易用,配置方便。 面试官:好,今天的面试就到这里。你对这些知识有一定的掌握,但在一些复杂问题上还可以更深入理解。回去等通知吧,我们会综合评估后给你答复。
问题答案
- ArrayList 和 LinkedList 的区别:
- 数据结构:ArrayList 基于动态数组实现,内存是连续的;LinkedList 基于双向链表实现,内存不连续。
- 随机访问:ArrayList 支持随机访问,通过索引直接定位元素,时间复杂度为 O(1);LinkedList 随机访问效率低,需要从头或尾遍历链表,时间复杂度为 O(n)。
- 插入和删除:ArrayList 在中间插入或删除元素时,需要移动大量元素,时间复杂度为 O(n);LinkedList 在中间插入或删除元素只需修改指针,时间复杂度为 O(1),但在获取元素位置时效率低。
- HashMap 在 JDK1.7 和 JDK1.8 中的区别:
- 数据结构:JDK1.7 采用数组 + 链表结构,JDK1.8 采用数组 + 链表 + 红黑树结构。当链表长度大于 8 且数组容量大于 64 时,链表会转换为红黑树,以提高查找效率。
- 插入方式:JDK1.7 采用头插法,新元素插入到链表头部;JDK1.8 采用尾插法,新元素插入到链表尾部,避免了多线程环境下头插法可能产生的环形链表问题。
- 扩容机制:JDK1.7 扩容时,需要重新计算每个元素的哈希值并重新插入到新的数组中;JDK1.8 优化了扩容机制,部分元素在扩容时不需要重新计算哈希值,直接根据原哈希值和新容量的关系决定是留在原位置还是移动到新位置。
- Spring 中 IOC 和 AOP:
- IOC(控制反转):将对象的创建和管理控制权从应用程序代码转移到 Spring 容器。通过依赖注入(DI),Spring 容器负责创建对象、管理对象的生命周期以及注入对象所依赖的其他对象。这样可以降低组件之间的耦合度,提高代码的可维护性和可测试性。
- AOP(面向切面编程):将与业务逻辑无关但又横切多个业务模块的功能(如日志记录、事务管理、权限控制等)提取出来,形成独立的切面。通过动态代理或字节码增强等技术,在不修改原有业务代码的情况下,将切面功能织入到目标方法的执行过程中,实现功能的复用和系统的解耦。
- 线程池的核心参数:
- corePoolSize(核心线程数):线程池中始终存活的线程数,即使这些线程处于空闲状态,也不会被销毁。当有新任务提交时,如果线程池中的线程数小于 corePoolSize,会创建新的线程来处理任务。
- maximumPoolSize(最大线程数):线程池中允许的最大线程数。当任务队列已满且线程池中的线程数小于 maximumPoolSize 时,会创建新的线程来处理任务。
- keepAliveTime(线程存活时间):当线程池中的线程数大于 corePoolSize 时,多余的空闲线程在存活时间达到 keepAliveTime 后会被销毁。
- unit(存活时间单位):keepAliveTime 的时间单位,如 TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)等。
- workQueue(任务队列):用于存放等待执行的任务。常用的任务队列有 ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)、SynchronousQueue(同步队列)等。
- CountDownLatch:
- 作用:用于协调多个线程之间的同步,允许一个或多个线程等待其他一组线程完成操作后再继续执行。
- 使用方法:创建一个 CountDownLatch 对象并设置初始计数(例如初始值为 n)。主线程或其他需要等待的线程调用 await() 方法进入等待状态,直到计数为 0。其他线程在完成任务后调用 countDown() 方法,将计数减 1。当计数减为 0 时,所有等待的线程被唤醒继续执行。
- MyBatis 中 #{} 和 ${} 的区别:
- #{}:预编译方式,MyBatis 会将 SQL 中的 #{} 替换为?,并使用 PreparedStatement 设置参数值,能有效防止 SQL 注入。例如:
SELECT * FROM user WHERE username = #{username}。 - **{} 中的内容替换到 SQL 中,可能存在 SQL 注入风险。例如:
SELECT * FROM user WHERE username = '${username}',如果传入的 username 为'; DROP TABLE user; --,则会导致数据库表被删除。一般用于传入数据库对象,如表名、列名等。
- #{}:预编译方式,MyBatis 会将 SQL 中的 #{} 替换为?,并使用 PreparedStatement 设置参数值,能有效防止 SQL 注入。例如:
- Dubbo 框架:
- 作用:是一款高性能的 Java 分布式服务框架,用于实现分布式系统中的服务治理。它提供了服务的注册与发现、负载均衡、远程调用、服务监控等功能,帮助开发者构建高性能、可扩展的分布式应用。
- 工作原理:
- 服务注册:服务提供者启动时,将自己提供的服务注册到注册中心(如 Zookeeper)。
- 服务发现:服务消费者启动时,从注册中心获取服务提供者的地址列表。
- 远程调用:服务消费者通过代理对象调用远程服务,Dubbo 框架负责将调用请求发送到服务提供者,并将响应结果返回给消费者。在这个过程中,Dubbo 会进行负载均衡,从服务提供者列表中选择一个合适的提供者处理请求。
- RabbitMQ 的消息模型和应用场景:
- 消息模型:
- Direct(直连模型):消息通过交换机根据路由键(routing key)直接发送到对应的队列。如果队列绑定的路由键与消息的路由键完全匹配,消息就会被发送到该队列。
- Topic(主题模型):消息通过交换机根据路由模式(routing pattern)发送到队列。路由模式支持通配符,如
*.log表示匹配所有以.log结尾的路由键,能更灵活地匹配队列。 - Fanout(扇形模型):消息发送到交换机后,会被广播到所有绑定的队列,不考虑路由键。
- Headers(头模型):通过消息头中的键值对来匹配队列,而不是路由键。
- 应用场景:
- 异步处理:将一些耗时的操作(如发送邮件、生成报表等)放入消息队列,由消费者异步处理,提高系统响应速度。
- 解耦系统:不同模块之间通过消息队列进行通信,降低模块之间的耦合度,使系统更易于维护和扩展。
- 流量削峰:在高并发场景下,将请求放入消息队列,消费者按一定速率从队列中获取请求处理,避免瞬间高流量对系统造成压力。
- 消息模型:
- xxl - job 分布式任务调度框架:
- 核心功能:
- 任务管理:提供可视化界面,方便用户管理任务,包括任务的新增、修改、删除、暂停、恢复等操作。
- 任务调度:支持多种调度方式,如 cron 表达式调度、固定频率调度等,能精确控制任务的执行时间。
- 任务执行:负责将任务分配到具体的执行器节点上执行,并监控任务的执行状态。
- 日志管理:记录任务执行的详细日志,方便用户排查问题。
- 优势:
- 轻量级:架构简单,部署方便,对系统资源消耗小。
- 易用性:提供简洁易用的可视化界面,降低了任务调度的使用门槛。
- 高可用:支持集群部署,保证任务调度的可靠性。
- 扩展性:可以方便地扩展执行器节点,以应对不同规模的任务处理需求。
- 核心功能: