面试官:请简要介绍一下Java中的多线程,以及如何创建一个线程。
王铁牛:多线程就是在一个程序中同时运行多个线程嘛。创建线程可以通过继承Thread类或者实现Runnable接口。
面试官:那说说线程池的作用和几种常见的线程池类型。
王铁牛:线程池可以复用线程,提高效率。常见的有FixedThreadPool、CachedThreadPool、ScheduledThreadPool等。
面试官:好,第一轮表现不错。接下来问几个关于JVM的问题,类加载机制分哪几个阶段?
王铁牛:嗯……好像是加载、验证、准备、解析、初始化。
面试官:对,回答正确。那JVM内存区域主要有哪些?
王铁牛:有堆、栈、方法区、程序计数器、本地方法栈。
面试官:很好。最后一个,垃圾回收算法有哪些?
王铁牛:这个……有标记清除、标记整理、复制算法。
面试官:第二轮整体还行。现在问一些关于Spring的问题,Spring的核心特性是什么?
王铁牛:控制反转和面向切面编程。
面试官:那Spring Boot的优势在哪里?
王铁牛:好像是简化配置,快速搭建项目。
面试官:说说Spring MVC的工作流程。
王铁牛:这个……不太清楚。
面试官:第三轮了啊。说说MyBatis的缓存机制。
王铁牛:有一级缓存和二级缓存。
面试官:Dubbo的集群容错策略有哪些?
王铁牛:这个……我不太确定。
面试官:最后一个问题,RabbitMq的消息确认机制是怎样的?
王铁牛:啊……我真不知道。
面试官:今天的面试就到这里,回去等通知吧。
答案:
- 多线程创建:
- 继承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类的构造函数并调用start方法启动线程。例如:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("通过Runnable接口创建的线程");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
- 线程池:
- FixedThreadPool:固定大小的线程池,线程数量一旦创建就不会改变。适用于需要控制线程数量,且任务量相对稳定的场景。
- CachedThreadPool:可缓存的线程池,线程数量可根据需求动态变化。如果线程池中的线程在一定时间内没有被使用,就会被回收。适用于执行短期异步任务的场景。
- ScheduledThreadPool:定时线程池,可定时执行任务。适用于需要按照一定时间间隔执行任务的场景。
- JVM类加载机制:
- 加载:通过类的全限定名来获取定义此类的二进制字节流,将字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
- 验证:确保加载的类的正确性,包括文件格式验证、元数据验证、字节码验证、符号引用验证等。
- 准备:为类的静态变量分配内存,并将其初始化为默认值。
- 解析:将常量池内的符号引用替换为直接引用的过程。
- 初始化:执行类构造器()方法的过程,()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。
- JVM内存区域:
- 堆:是Java虚拟机所管理的内存中最大的一块,被所有线程共享,用于存放对象实例。
- 栈:每个线程都有自己独立的栈,用于存放局部变量、方法调用等。
- 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 程序计数器:是一块较小的内存空间,它记录着当前线程执行的字节码的行号指示器。
- 本地方法栈:与虚拟机栈所发挥的作用非常相似,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
- 垃圾回收算法:
- 标记清除算法:分为两个阶段,标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。缺点是会产生大量不连续的内存碎片。
- 标记整理算法:标记阶段和标记清除算法一样,但是在清除阶段,它不是简单地回收对象,而是将存活的对象向一端移动,然后直接清理掉端边界以外的内存。
- 复制算法:将内存分为大小相等的两块,每次只使用其中一块,当这一块内存用完了,就将还存活的对象复制到另一块上面,然后把已使用的内存空间一次清理掉。适用于对象存活率低的场景。
- Spring核心特性:
- 控制反转(IoC):将对象的创建和依赖关系的管理从传统的程序代码中分离出来,交给Spring容器来负责,实现了对象之间的解耦。
- 面向切面编程(AOP):通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。可以在不修改原有代码的基础上,为业务逻辑添加额外的功能,如日志记录、事务管理等。
- Spring Boot优势:
- 简化配置:Spring Boot通过自动配置和约定大于配置的原则,极大地减少了繁琐的XML配置文件,让开发者可以更专注于业务逻辑的实现。
- 快速搭建项目:提供了各种starter依赖,开发者只需引入相关依赖,就能快速搭建起一个完整的项目框架,提高开发效率。
- Spring MVC工作流程:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,根据请求信息找到对应的Handler。
- HandlerMapping返回对应的Handler给DispatcherServlet,DispatcherServlet再调用HandlerAdapter处理器适配器。
- HandlerAdapter调用具体的Handler处理请求,并返回一个ModelAndView对象。
- DispatcherServlet根据返回的ModelAndView,调用ViewResolver视图解析器,解析出具体的视图。
- ViewResolver返回视图给DispatcherServlet,DispatcherServlet根据视图进行渲染,将渲染结果返回给客户端。
- MyBatis缓存机制:
- 一级缓存:是SqlSession级别的缓存,在同一个SqlSession中,执行相同的SQL语句,第一次查询会从数据库中查询数据并缓存起来,第二次查询时直接从缓存中获取数据,不会再查询数据库。当SqlSession关闭时,一级缓存会被清空。
- 二级缓存:是Mapper级别的缓存,多个SqlSession可以共享二级缓存。当一个Mapper的namespace下的方法被调用时,会先从二级缓存中查找数据,如果找不到再从数据库中查询,并将查询结果缓存到二级缓存中。二级缓存的生命周期比一级缓存长,它的清空需要手动调用SqlSessionFactory的clearCache方法或者通过配置文件设置缓存的刷新策略。
- Dubbo集群容错策略:
- Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
- Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作。
- Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多的资源。
- RabbitMq消息确认机制:
- 生产者确认:
- 开启方式:在创建连接工厂时设置publisher-confirms="true"。
- 工作原理:生产者将消息发送到RabbitMQ服务器后,服务器会返回一个确认消息给生产者,告知消息是否成功到达。如果设置了mandatory参数为true,当消息无法路由到指定队列时,会返回一个未路由到的确认消息给生产者。
- 消费者确认:
- 自动确认:消费者在接收到消息后,会自动向RabbitMQ服务器发送确认消息,表示已经接收到消息。这种方式简单但存在风险,如果消费者在处理消息过程中出现异常,消息会被认为已经处理,导致数据丢失。
- 手动确认:消费者在接收到消息后,不会自动确认,而是需要通过调用channel.basicAck方法手动发送确认消息。可以选择批量确认或者逐个确认,并且可以设置是否重回队列等参数,提高消息处理的可靠性。
- 生产者确认: