面试官:请简要介绍一下Java中的多线程,以及如何创建一个线程。
王铁牛:多线程就是在一个程序中同时运行多个线程。创建线程可以通过继承Thread类或者实现Runnable接口。
面试官:回答得不错。那线程池有哪些优点,如何创建一个线程池?
王铁牛:线程池的优点有很多,比如提高响应速度、降低资源消耗等。创建线程池可以通过ThreadPoolExecutor类来实现。
面试官:很好。接下来谈谈HashMap的底层实现原理。
王铁牛:HashMap是基于数组和链表实现的,当发生哈希冲突时会将元素添加到链表中。
第一轮结束。
面试官:说说Spring框架的核心特性。
王铁牛:Spring框架的核心特性有依赖注入、面向切面编程等。
面试官:那Spring Boot与Spring相比有哪些优势?
王铁牛:Spring Boot更加简单易用,它可以快速搭建一个Spring应用。
面试官:讲讲MyBatis的工作原理。
王铁牛:MyBatis通过XML或注解配置SQL语句,然后通过反射机制执行SQL。
第二轮结束。
面试官:Dubbo的服务调用流程是怎样的?
王铁牛:呃……这个不太清楚。
面试官:RabbitMq如何保证消息的可靠性?
王铁牛:嗯……大概是通过一些机制吧,具体不太明白。
面试官:xxl-job的调度原理是什么?
王铁牛:这个也说不太清。
第三轮结束。
面试结束后,面试官表示会让王铁牛回家等通知。虽然王铁牛在一些简单问题上回答得不错,但对于复杂问题的回答比较模糊,整体表现还有待提高。
答案:
- 多线程及创建线程:
- 多线程是指在一个程序中同时运行多个线程,每个线程可以独立执行不同的任务。
- 创建线程的方式有两种:
- 继承Thread类:定义一个类继承Thread类,并重写run方法,在run方法中编写线程要执行的代码。然后通过创建该类的实例并调用start方法来启动线程。例如:
class MyThread extends Thread { @Override public void run() { System.out.println("线程执行的代码"); } } MyThread thread = new MyThread(); thread.start(); - 实现Runnable接口:定义一个类实现Runnable接口,并重写run方法。然后通过创建Thread类的实例,并将实现了Runnable接口的类的实例作为参数传递给Thread类的构造函数,最后调用start方法启动线程。例如:
class MyRunnable implements Runnable { @Override public void run() { System.out.println("线程执行的代码"); } } MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start();
- 继承Thread类:定义一个类继承Thread类,并重写run方法,在run方法中编写线程要执行的代码。然后通过创建该类的实例并调用start方法来启动线程。例如:
- 线程池:
- 优点:
- 提高响应速度:当有新任务到来时,直接从线程池中获取线程执行,而不需要创建新线程,减少了创建线程的开销,从而提高了响应速度。
- 降低资源消耗:线程池可以复用线程,避免了大量线程创建和销毁带来的资源消耗,如内存、CPU等。
- 便于管理:可以对线程池中的线程进行统一管理和控制,如设置线程数量上限、线程存活时间等。
- 创建线程池通过ThreadPoolExecutor类实现,示例代码如下:
ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy());- corePoolSize:线程池的核心线程数,当提交的任务数小于corePoolSize时,线程池会创建新线程来执行任务。
- maximumPoolSize:线程池的最大线程数,当提交的任务数大于corePoolSize且任务队列已满时,线程数会增加到maximumPoolSize,如果此时线程数已经达到maximumPoolSize,会根据拒绝策略处理新任务。
- keepAliveTime:线程池中非核心线程的存活时间,当线程数大于corePoolSize时,多余的线程在空闲时间超过keepAliveTime后会被销毁。
- TimeUnit:keepAliveTime的时间单位。
- new LinkedBlockingQueue():任务队列,用于存放提交的任务,当线程池中的线程数小于corePoolSize时,新任务会放入任务队列中。
- new ThreadPoolExecutor.CallerRunsPolicy():拒绝策略,当线程池和任务队列都满了,新任务会由调用execute方法的线程来执行。
- 优点:
- HashMap底层实现原理:
- HashMap是基于数组和链表(JDK 1.8之后还有红黑树)实现的。
- 它通过计算键值对的哈希值来确定元素在数组中的位置。当发生哈希冲突时,即两个或多个键值对的哈希值相同,会将元素添加到链表(JDK 1.8之前)或红黑树(JDK 1.8之后,当链表长度超过8且数组长度大于64时,链表会转换为红黑树)中。
- 例如,当向HashMap中添加一个键值对时,首先计算键的哈希值,然后通过哈希值与数组长度取模得到在数组中的索引位置。如果该位置为空,则直接将键值对放入该位置;如果不为空,则检查键是否相同,如果相同则覆盖值,如果不同则将新元素添加到链表或红黑树的末尾。
- Spring框架核心特性:
- 依赖注入:
- 依赖注入是指将对象的依赖关系通过外部方式注入到对象中,而不是在对象内部自行创建依赖对象。这样可以降低对象之间的耦合度,提高代码的可维护性和可测试性。
- 例如,一个类A依赖于类B,通过依赖注入,可以在类A的构造函数或方法中传入类B的实例,而不是在类A中直接创建类B的实例。
- 面向切面编程(AOP):
- AOP允许将一些横切关注点(如日志记录、事务管理等)与业务逻辑分离,通过动态代理等方式在运行时织入到业务逻辑中。
- 例如,在一个电商系统中,订单处理的业务逻辑和日志记录是两个不同的关注点,可以通过AOP将日志记录的逻辑在订单处理方法执行前后织入,而不需要在每个订单处理方法中都重复编写日志记录代码。
- 依赖注入:
- Spring Boot与Spring相比的优势:
- 快速搭建:Spring Boot提供了快速搭建Spring应用的能力,它通过自动配置等机制,简化了项目的配置过程,减少了大量的样板代码。开发人员可以更专注于业务逻辑的实现,而不需要花费大量时间在配置文件的编写上。
- 内置依赖管理:Spring Boot内置了依赖管理功能,它会自动管理项目中所依赖的各种库的版本,避免了版本冲突问题,使得项目的依赖管理更加简单和一致。
- 嵌入式服务器:Spring Boot可以很方便地嵌入各种服务器,如Tomcat、Jetty等,无需像传统Spring项目那样手动配置服务器,使得项目的部署更加便捷。
- MyBatis工作原理:
- MyBatis通过XML或注解配置SQL语句。
- 工作时,首先读取配置文件,解析其中的SQL语句和映射关系。
- 当调用MyBatis的方法执行SQL时,它会根据映射关系找到对应的SQL语句,然后通过反射机制创建参数对象,将参数设置到SQL语句中,执行SQL并返回结果。
- 例如,在一个MyBatis的XML配置文件中定义了一个SQL语句:
<select id="getUserById" parameterType="int" resultType="User"> SELECT * FROM user WHERE id = #{id} </select> - 在Java代码中调用该方法时,MyBatis会根据id找到对应的SQL语句,将传入的参数id设置到SQL中的#{id}位置,然后执行SQL查询数据库,并将结果封装成User对象返回。
- Dubbo服务调用流程:
- 服务注册:服务提供者启动时,将自己提供的服务信息(包括接口、实现类等)注册到注册中心。
- 服务发现:服务消费者启动时,从注册中心获取服务提供者的信息。
- 远程调用:服务消费者根据获取到的服务提供者信息,通过网络调用服务提供者的远程方法,传递参数并获取返回结果。
- 具体实现过程中,Dubbo通过动态代理机制生成代理对象,服务消费者调用代理对象的方法时,会将调用请求发送到远程服务提供者,远程服务提供者接收到请求后执行相应的业务逻辑,并将结果返回给服务消费者。
- RabbitMq保证消息可靠性:
- 生产者确认机制:生产者发送消息后,可以通过确认机制知道消息是否成功到达Broker。可以选择事务模式或发送方确认模式。
- 事务模式:生产者发送消息前开启事务,发送消息后提交事务,如果事务提交失败则回滚,消息会重新发送。但事务模式性能较低。
- 发送方确认模式:生产者发送消息后,Broker接收到消息会返回确认结果给生产者,生产者根据确认结果决定是否重发消息。
- 消息持久化:在Broker端,将队列和消息都设置为持久化,这样即使Broker重启,消息也不会丢失。
- 消费者确认机制:消费者接收到消息后,通过确认机制告知Broker消息已被正确处理。可以选择自动确认或手动确认。
- 自动确认:消费者接收到消息后,自动认为消息已被处理,Broker会将消息标记为已消费并删除。
- 手动确认:消费者接收到消息后,需要手动调用确认方法告知Broker消息已被处理,这样可以保证消息在消费者真正处理完后才被删除,避免消息丢失。
- 生产者确认机制:生产者发送消息后,可以通过确认机制知道消息是否成功到达Broker。可以选择事务模式或发送方确认模式。
- xxl - job调度原理:
- 调度中心:是xxl - job的核心组件,负责管理任务调度规则、触发任务执行等。它存储了所有的任务信息和调度策略。
- 执行器:负责实际执行任务,它与调度中心进行通信,接收调度中心发送的任务执行请求,并执行相应的任务逻辑。
- 调度流程:调度中心按照预先设置的调度规则,定时触发任务执行请求。执行器接收到任务请求后,根据任务配置的执行逻辑执行任务。任务执行完成后,执行器将执行结果反馈给调度中心。
- 例如,在调度中心配置了一个定时任务,每天凌晨1点执行数据备份任务。到了凌晨1点,调度中心会向对应的执行器发送任务执行请求,执行器接收到请求后,调用数据备份的业务逻辑进行数据备份,备份完成后将备份结果(成功或失败)反馈给调度中心。