面试官:欢迎你来面试,第一轮先来几个基础问题热热身。首先,讲讲Java中的多线程有哪些实现方式?
王铁牛:多线程实现方式有继承Thread类、实现Runnable接口和实现Callable接口。
面试官:不错,回答正确。那说说线程池的几个重要参数及其作用。
王铁牛:线程池有corePoolSize、maximumPoolSize、keepAliveTime、unit和workQueue这些参数。corePoolSize是核心线程数,maximumPoolSize是最大线程数,keepAliveTime是线程池线程空闲后的存活时间,unit是时间单位,workQueue是任务队列。
面试官:很好。再问一个,ArrayList和HashMap的底层实现原理了解吗?
王铁牛:ArrayList是基于数组实现的,它有一个数组用来存储元素,支持随机访问。HashMap是基于数组+链表+红黑树实现的,通过key的哈希值来确定存储位置。
面试官:好,第一轮面试结束。下面进入第二轮,稍微难点的问题。先说说Spring框架中IoC和AOP的概念。
王铁牛:IoC就是控制反转,把对象的创建和依赖注入交给Spring容器来管理。AOP是面向切面编程,在不修改原有代码的基础上,动态地给程序添加功能。
面试官:还算靠谱。那Spring Boot的自动配置原理是什么?
王铁牛:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,它会根据类路径下的依赖来自动配置各种组件。
面试官:勉强及格。再问个关于MyBatis的,MyBatis的动态SQL有哪些?
王铁牛:有if、where、trim、choose、when、otherwise、foreach这些。
面试官:第二轮结束。最后一轮,来点更复杂的。讲讲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();
- 实现Callable接口:创建一个类实现Callable接口,重写call方法。通过FutureTask类将实现Callable接口的类的实例对象作为参数传入创建FutureTask对象,再将FutureTask对象作为参数传入Thread类的构造函数创建Thread对象并调用start方法启动线程。并且可以通过FutureTask对象的get方法获取线程执行的返回值。例如:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "线程执行结果";
}
}
MyCallable callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
- 线程池参数及其作用:
- corePoolSize:核心线程数,当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
- maximumPoolSize:最大线程数,当提交的任务数大于corePoolSize且任务队列已满时,线程池会创建新的线程来执行任务,直到线程数达到maximumPoolSize。
- keepAliveTime:线程池线程空闲后的存活时间,当线程空闲时间超过keepAliveTime时,非核心线程会被销毁。
- unit:时间单位,用于指定keepAliveTime的时间单位。
- workQueue:任务队列,用于存储提交的任务,当线程池线程都在忙碌时,新提交的任务会被放入任务队列中。
- ArrayList底层实现原理:
- ArrayList是基于数组实现的。它有一个数组用来存储元素,支持随机访问。
- 当添加元素时,如果数组容量不足,会进行扩容。扩容时会创建一个新的更大的数组,将原数组的元素复制到新数组中。
- ArrayList是非线程安全的。
- HashMap底层实现原理:
- HashMap是基于数组+链表+红黑树实现的。
- 通过key的哈希值来确定存储位置,计算哈希值后通过取模运算得到数组的索引。
- 如果该索引位置为空,则直接插入新的键值对。如果不为空,则会比较key是否相同,如果相同则更新value,如果不同则将新的键值对添加到链表或红黑树中。
- 当链表长度达到8且数组容量大于等于64时,链表会转换为红黑树,以提高查询效率。
- HashMap是非线程安全的。
- Spring框架中IoC和AOP的概念:
- IoC(控制反转):把对象的创建和依赖注入交给Spring容器来管理。例如,在传统方式中,一个类需要使用另一个类时,需要在本类中创建该类的实例。而在IoC中,由Spring容器来创建和注入这些依赖对象。
- AOP(面向切面编程):在不修改原有代码的基础上,动态地给程序添加功能。比如可以通过AOP实现日志记录、事务管理等功能。可以将这些通用功能封装成切面,在程序执行的特定切入点执行这些切面逻辑。
- Spring Boot的自动配置原理:
- Spring Boot通过@EnableAutoConfiguration注解开启自动配置。
- 它会根据类路径下的依赖来自动配置各种组件。例如,当项目中引入了Spring Data JPA的依赖,Spring Boot会自动配置好数据库连接、JPA相关的配置等,开发者只需要按照规范编写实体类和Repository接口即可使用数据库操作功能。
- 自动配置是通过大量的@Configuration类和@Bean注解来实现的,这些配置类会根据不同的条件进行自动配置。
- MyBatis的动态SQL:
- if:根据条件判断是否拼接SQL语句。例如:
<select id="selectUser" parameterType="map" resultType="User">
SELECT * FROM user
<where>
<if test="username != null">
AND username = #{username}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
- where:用于动态生成WHERE子句,并且会自动去掉第一个AND或OR。
- trim:可以自定义前缀和后缀。例如:
<select id="selectUser" parameterType="map" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR">
<if test="username != null">
AND username = #{username}
</if>
<if test="age != null">
AND age = #{age}
</if>
</trim>
</select>
- choose、when、otherwise:类似Java中的switch语句,根据条件选择一个分支执行。例如:
<select id="selectUser" parameterType="map" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="username != null">
AND username = #{username}
</when>
<when test="age != null">
AND age = #{age}
</when>
<otherwise>
AND status = 'active'
</otherwise>
</choose>
</where>
</select>
- foreach:用于循环遍历集合,通常用于IN语句。例如:
<select id="selectUserList" parameterType="list" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
- Dubbo的集群容错机制:
- Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
- Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
- RabbitMq的消息确认机制:
- 生产者确认机制:
- 开启发布确认:在创建连接工厂时设置publisher-confirms="true"。
- 同步确认:生产者发送消息后等待Broker的确认,通过ConfirmCallback回调函数获取确认结果。例如:
- 生产者确认机制:
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("消息已确认,deliveryTag: " + deliveryTag);
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("消息未确认,deliveryTag: " + deliveryTag);
}
});
- **异步确认**:使用线程池和ConcurrentNavigableMap来管理未确认的消息,通过ConfirmCallback回调函数获取确认结果。例如:
channel.confirmSelect();
final ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if (multiple) {
ConcurrentNavigableMap<Long, String> headMap = outstandingConfirms.headMap(deliveryTag, true);
headMap.clear();
} else {
outstandingConfirms.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
if (multiple) {
ConcurrentNavigableMap<Long, String> headMap = outstandingConfirms.headMap(deliveryTag, true);
headMap.clear();
} else {
String message = outstandingConfirms.get(deliveryTag);
System.out.println("未确认消息: " + message);
outstandingConfirms.remove(deliveryTag);
}
}
});
// 发送消息并记录
long nextSeqNo = channel.getNextPublishSeqNo();
outstandingConfirms.put(nextSeqNo, "消息内容");
channel.basicPublish("", "队列名", null, "消息内容".getBytes());
- 消费者确认机制:
- 自动确认:消费者在接收到消息后,自动确认消息已被接收。在创建消费者时设置autoAck=true。
- 手动确认:消费者在接收到消息后,手动调用basicAck方法确认消息已被接收。例如:
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("接收到消息: " + message);
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
boolean autoAck = false;
channel.basicConsume("队列名", autoAck, "消费者标签", consumer);
- xxl-job的执行器原理:
- 调度中心:负责管理任务调度规则、触发调度运行,并提供统一的任务管理平台。
- 执行器:负责接收调度中心的调度请求并执行任务。
- 执行器启动时会向调度中心注册,调度中心会定期心跳检测执行器的存活状态。
- 当调度中心触发任务时,会将任务信息发送给对应的执行器,执行器根据任务信息调用具体的业务逻辑代码来执行任务。执行器可以是一个独立的应用程序,也可以是集成在其他项目中的一个模块。