面试官:欢迎你来面试,先简单介绍一下你自己吧。
王铁牛:面试官您好,我叫王铁牛,有几年Java开发经验,熟悉各种Java技术。
面试官:第一轮提问开始。首先,讲讲Java中的多线程有哪些实现方式?
王铁牛:有继承Thread类、实现Runnable接口、实现Callable接口这几种方式。
面试官:不错,回答正确。那说说线程池的核心参数都有哪些?
王铁牛:有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
面试官:很好。再问一个,HashMap在JDK1.8之后的底层实现有哪些改进?
王铁牛:在JDK1.8之后,HashMap底层采用数组+链表+红黑树的结构,当链表长度大于8且数组长度大于64时,链表会转换为红黑树。
面试官:第一轮表现不错,接下来第二轮提问。说说Spring的核心特性有哪些?
王铁牛:Spring的核心特性有依赖注入、面向切面编程、IoC容器等。
面试官:那Spring Boot有什么优势?
王铁牛:Spring Boot简化了Spring应用的开发,内置了很多常用的依赖,能快速搭建项目。
面试官:讲讲MyBatis的工作原理。
王铁牛:就是通过XML配置文件或注解定义SQL语句,然后通过SqlSessionFactory创建SqlSession,再通过SqlSession执行SQL操作。
面试官:第二轮也还可以,进入第三轮提问。Dubbo的集群容错策略有哪些?
王铁牛:有failover、failfast、failsafe、failback、forking等。
面试官:RabbitMq的消息确认机制是怎样的?
王铁牛:有生产者确认和消费者确认,生产者确认又分为事务模式和发送方确认模式,消费者确认有自动确认和手动确认。
面试官:xxl-job的执行流程能说一下吗?
王铁牛:大概就是任务调度中心触发任务,执行器接收任务并执行。
面试官:今天的面试就到这里,回去等通知吧。
答案:
- 多线程实现方式:
- 继承Thread类:通过继承Thread类并重写run方法来定义线程执行的任务。每个Thread类的实例都是一个独立的线程。这种方式的缺点是Java不支持多重继承,如果继承了Thread类,就不能再继承其他类。
- 实现Runnable接口:实现Runnable接口的类需要实现run方法,然后将该类的实例作为参数传递给Thread类的构造函数来创建线程。这种方式避免了单继承的限制,更适合多个线程共享资源的场景。
- 实现Callable接口:Callable接口类似于Runnable接口,但它的call方法有返回值,并且可以抛出异常。通过FutureTask类来包装Callable任务,然后将FutureTask作为参数传递给Thread类来创建线程。常用于需要获取线程执行结果的场景。
- 线程池核心参数:
- corePoolSize:线程池的核心线程数,当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
- maximumPoolSize:线程池的最大线程数,当提交的任务数大于corePoolSize时,会将任务放入workQueue中,如果workQueue已满且线程数小于maximumPoolSize,则会创建新的线程来执行任务。
- keepAliveTime:线程池中的线程在空闲时的存活时间,当线程空闲时间超过keepAliveTime时,线程会被销毁,直到线程数等于corePoolSize。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列,用于存放提交的任务,当线程数大于等于corePoolSize时,新提交的任务会放入workQueue中。
- threadFactory:线程工厂,用于创建线程,可自定义线程的名称、优先级等属性。
- handler:拒绝策略,当线程数达到maximumPoolSize且workQueue已满时,新提交的任务会被handler处理,默认的拒绝策略是抛出RejectedExecutionException异常。
- HashMap在JDK1.8之后的底层实现改进:
- 数据结构:采用数组+链表+红黑树的结构。在JDK1.8之前,HashMap底层是数组+链表的结构,当链表长度较长时,查找效率会降低。在JDK1.8之后,当链表长度大于8且数组长度大于64时,链表会转换为红黑树,红黑树的查找效率更高。
- 哈希算法:JDK1.8对哈希算法进行了优化,提高了哈希值的计算效率和分布均匀性,减少了哈希冲突的发生。
- 扩容机制:扩容时,新数组的大小是原数组的2倍。扩容时会重新计算每个元素的哈希值,并将其放入新数组的相应位置,可能会导致链表的顺序发生变化。
- Spring的核心特性:
- 依赖注入(Dependency Injection):通过IoC容器将对象之间的依赖关系进行管理和注入,降低了对象之间的耦合度。例如,在一个类中需要使用另一个类的对象时,不需要在该类中手动创建,而是由IoC容器注入。
- 面向切面编程(Aspect-Oriented Programming,AOP):允许将一些横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,以更灵活的方式进行处理。通过切面(Aspect)、切点(Pointcut)和通知(Advice)等概念来实现。
- IoC容器:负责创建、配置和管理对象之间的依赖关系。IoC容器通过读取配置文件或注解来确定对象之间的依赖关系,并进行实例化和注入。常见的IoC容器实现有Spring容器。
- Spring Boot的优势:
- 简化开发:Spring Boot通过自动配置和约定大于配置的原则,极大地简化了Spring应用的开发过程。开发者只需关注业务逻辑,无需手动配置大量的XML文件或繁琐的依赖。
- 内置依赖:内置了大量常用的依赖,如Spring、Spring MVC、Tomcat等,避免了手动添加依赖的麻烦,同时保证了依赖的版本兼容性。
- 快速搭建项目:提供了多种项目初始化工具,如Spring Initializr,可以快速生成一个包含基本结构和依赖的Spring Boot项目骨架,节省开发时间。
- 微服务支持:对微服务架构有良好的支持,提供了诸如服务注册与发现、配置管理等功能,方便构建分布式系统。
- 监控与管理:自带了一些监控和管理端点,如健康检查、指标统计等,方便对应用的运行状态进行监控和管理。
- MyBatis的工作原理:
- 配置SQL语句:通过XML配置文件或注解定义SQL语句。在XML配置文件中,可以使用SQL标签来编写SQL语句,包括查询、插入、更新、删除等操作。
- 创建SqlSessionFactory:通过MyBatis的配置文件创建SqlSessionFactory对象,该对象是MyBatis的核心,用于创建SqlSession。
- 创建SqlSession:通过SqlSessionFactory创建SqlSession对象,SqlSession是执行SQL操作的主要接口。
- 执行SQL操作:通过SqlSession对象调用相应的方法来执行SQL操作,如selectOne、selectList、insert、update、delete等方法。在执行查询操作时,MyBatis会根据配置的SQL语句从数据库中查询数据,并将结果映射为Java对象返回。
- 结果映射:MyBatis通过映射文件将查询结果映射为Java对象。映射文件中定义了SQL查询结果与Java对象属性之间的对应关系,通过这种映射,MyBatis可以将数据库中的数据转换为Java对象,方便在应用程序中使用。
- Dubbo的集群容错策略:
- failover:失败自动切换,当调用失败时,会自动重试其他服务器,默认的重试次数是2次。适用于读操作等对结果准确性要求不是特别高的场景。
- failfast:快速失败,当调用失败时,立即抛出异常,不再重试。适用于写操作等对数据一致性要求较高的场景,避免重复写入导致数据不一致。
- failsafe:失败安全,当调用失败时,直接忽略,不抛出异常。适用于对结果不敏感的操作,如日志记录等。
- failback:失败自动恢复,当调用失败时,会在后台异步重试,适用于一些对实时性要求不高,但需要保证最终成功的操作。
- forking:并行调用多个服务器,只要有一个成功就返回,用于需要快速获取结果的场景,比如实时性要求较高的查询操作。
- RabbitMq的消息确认机制:
- 生产者确认:
- 事务模式:生产者发送消息之前开启事务(channel.txSelect()),然后发送消息。如果消息发送成功,提交事务(channel.txCommit());如果发送失败,回滚事务(channel.txRollback())。这种方式保证了消息的可靠性,但性能较低,因为每次发送消息都需要进行事务操作。
- 发送方确认模式:生产者将信道设置为 confirm 模式(channel.confirmSelect()),然后发送消息。发送方可以通过监听 ConfirmListener 接口的回调方法来确认消息是否发送成功。如果发送成功,会调用 handleAck 方法;如果发送失败,会调用 handleNack 方法。发送方确认模式提供了更细粒度的控制,并且性能比事务模式高。
- 消费者确认:
- 自动确认:消费者接收到消息后,RabbitMQ会自动将该消息标记为已确认,从队列中删除。这种方式简单,但如果消费者在处理消息过程中出现异常,消息会丢失。
- 手动确认:消费者接收到消息后,不会自动确认,而是需要调用 channel.basicAck 方法手动确认消息。这样可以确保在消费者处理完消息后再确认,避免消息丢失。如果消费者在处理消息过程中出现异常,可以通过拒绝消息(channel.basicReject 或 channel.basicNack)并重新放回队列等方式来处理。
- 生产者确认:
- xxl-job的执行流程:
- 任务调度中心:负责管理任务的调度策略、执行计划等信息。它可以配置任务的触发时间(如定时执行、固定时间间隔执行等)、任务的参数等。
- 触发任务:当任务的触发时间到达时,任务调度中心会根据配置的调度策略触发相应的任务。
- 执行器接收任务:执行器是实际执行任务的组件,它监听任务调度中心的指令。当接收到任务调度中心触发的任务时,执行器会获取任务的相关信息,如任务的类名、参数等。
- 执行任务:执行器根据获取到的任务信息,加载对应的任务类,并调用其执行方法来执行任务。任务执行过程中可能会涉及到与数据库交互、调用其他服务等操作。执行完成后,执行器会将任务的执行结果反馈给任务调度中心。
面试总结:这次面试涵盖了Java多个重要的知识点,从基础的多线程、线程池到常用的框架如Spring、Spring Boot、MyBatis,以及分布式相关的Dubbo、消息队列RabbitMq和任务调度xxl-job等。王铁牛在回答过程中,对于一些简单问题能够准确回答,展现出了一定的基础知识储备。但在面对一些复杂问题时,回答的清晰度和完整性还有所欠缺。整体来看,王铁牛有一定的Java开发经验,但在技术细节和深入理解方面还有提升空间。希望他能在等待通知的过程中,进一步巩固和深化相关知识,为未来可能的工作做好更充分的准备。