《互联网大厂Java求职者面试大揭秘:核心知识与复杂场景问答》

57 阅读8分钟

面试官:欢迎你来面试,第一轮先来几个基础问题热热身。首先,讲讲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的执行器原理
    • 调度中心:负责管理任务调度规则、触发调度运行,并提供统一的任务管理平台。
    • 执行器:负责接收调度中心的调度请求并执行任务。
    • 执行器启动时会向调度中心注册,调度中心会定期心跳检测执行器的存活状态。
    • 当调度中心触发任务时,会将任务信息发送给对应的执行器,执行器根据任务信息调用具体的业务逻辑代码来执行任务。执行器可以是一个独立的应用程序,也可以是集成在其他项目中的一个模块。