《互联网大厂Java求职者面试大挑战:核心知识全考验》

47 阅读10分钟

面试官:欢迎你来面试,先简单介绍一下你自己吧。

王铁牛:面试官您好,我叫王铁牛,有几年Java开发经验,熟悉一些常见的框架和技术。

面试官:好,那第一轮开始,说说Java中多线程的实现方式有哪些?

王铁牛:可以通过继承Thread类,或者实现Runnable接口,还有实现Callable接口这几种方式。

面试官:回答得不错。那线程池的核心参数都有哪些,分别有什么作用?

王铁牛:核心参数有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。corePoolSize是核心线程数,maximumPoolSize是最大线程数,keepAliveTime是线程池线程空闲后的存活时间,unit是时间单位,workQueue是任务队列,threadFactory是线程工厂,handler是拒绝策略。

面试官:嗯,理解得挺到位。再问一个,简述一下HashMap的底层实现原理。

王铁牛:HashMap底层是数组+链表+红黑树。当链表长度大于8且数组长度大于64时,链表会转换为红黑树。插入元素时,先通过哈希值计算出在数组中的位置,如果该位置为空则直接插入,如果不为空则遍历链表或红黑树找到对应的键值对进行更新或插入新节点。

面试官:第一轮表现不错。接下来第二轮,讲讲Spring框架中IoC和AOP的概念。

王铁牛:IoC就是控制反转,把对象的创建和依赖关系管理交给Spring容器。AOP是面向切面编程,通过动态代理在不修改原有代码的基础上增强功能。

面试官:那Spring Boot的自动配置原理是什么?

王铁牛:Spring Boot通过条件注解来实现自动配置,根据项目中引入的依赖和配置信息,自动配置相应的组件和功能。

面试官:MyBatis的动态SQL有哪些?

王铁牛:有if、where、set、foreach等。if用于条件判断,where可以自动处理where子句,set用于更新语句中处理set部分,foreach用于循环遍历集合。

面试官:第二轮也还可以。最后第三轮,Dubbo的集群容错策略有哪些?

王铁牛:有failover、failfast、failsafe、failback、forking等。failover是失败重试,failfast是快速失败,failsafe是失败安全,failback是失败自动恢复,forking是并行调用多个服务取第一个成功结果。

面试官:RabbitMq的消息确认机制了解吗?

王铁牛:不太清楚。

面试官:xxl-job的核心组件有哪些?

王铁牛:回答得比较模糊,不太能清晰说出来。

面试官:Redis的数据结构有哪些,简单说几个。

王铁牛:就知道有字符串,其他的不太记得了。

面试结束,王铁牛在基础知识方面表现尚可,但在一些复杂问题上回答得不够清晰准确。整体来看,对技术的掌握还不够深入和全面。回家等通知吧,如果有进一步的结果会再联系你。

答案:

  1. Java多线程实现方式
    • 继承Thread类:创建一个类继承Thread类,重写run方法,然后创建该类的实例调用start方法启动线程。例如:
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("This is a thread by extending Thread.");
        }
    }
    MyThread thread = new MyThread();
    thread.start();
    
    • 实现Runnable接口:创建一个类实现Runnable接口,实现run方法,然后将该类实例作为参数传递给Thread类的构造函数创建线程对象并启动。例如:
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("This is a thread by implementing Runnable.");
        }
    }
    Thread thread = new Thread(new MyRunnable());
    thread.start();
    
    • 实现Callable接口:创建一个类实现Callable接口,实现call方法,该方法有返回值。通过FutureTask类包装Callable对象,再将FutureTask对象作为参数传递给Thread类的构造函数创建线程并启动。通过Future对象获取线程执行结果。例如:
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "This is a thread by implementing Callable.";
        }
    }
    FutureTask<String> task = new FutureTask<>(new MyCallable());
    Thread thread = new Thread(task);
    thread.start();
    try {
        String result = task.get();
        System.out.println(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
  2. 线程池核心参数及作用
    • corePoolSize:核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
    • maximumPoolSize:最大线程数。当提交的任务数大于corePoolSize且任务队列已满时,会创建新线程直到线程数达到maximumPoolSize。
    • keepAliveTime:线程池线程空闲后的存活时间。当线程空闲时间超过keepAliveTime,线程会被销毁(如果线程数大于corePoolSize)。
    • unit:keepAliveTime的时间单位。
    • workQueue:任务队列。用于存放提交的任务,当线程池线程忙碌时,新提交的任务会放入任务队列。
    • threadFactory:线程工厂。用于创建线程,可自定义线程的名称、优先级等属性。
    • handler:拒绝策略。当线程数达到maximumPoolSize且任务队列已满时,会调用拒绝策略来处理新提交的任务。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(调用者线程执行任务)、DiscardPolicy(丢弃新提交的任务)、DiscardOldestPolicy(丢弃任务队列中最旧的任务)。
  3. HashMap底层实现原理
    • HashMap底层是数组+链表+红黑树。
    • 哈希计算:首先通过对象的hashCode方法计算出哈希值,然后对哈希值进行扰动处理,使哈希值分布更均匀。
    • 数组存储:根据扰动后的哈希值计算出在数组中的索引位置。如果该位置为空,则直接插入新节点。
    • 链表处理:如果该位置不为空,则说明发生了哈希冲突,会遍历链表或红黑树(当链表长度大于8且数组长度大于64时,链表会转换为红黑树)。如果找到相同键的节点,则更新其值;如果未找到,则在链表或红黑树末尾插入新节点。
  4. Spring框架中IoC和AOP的概念
    • IoC(控制反转):把对象的创建和依赖关系管理交给Spring容器。传统方式是对象内部自己创建依赖对象,而IoC容器负责创建对象并注入依赖,降低了对象之间的耦合度。例如,一个Service类依赖于一个Dao类,在IoC模式下,由Spring容器创建Dao对象并注入到Service类中。
    • AOP(面向切面编程):通过动态代理在不修改原有代码的基础上增强功能。比如日志记录、事务管理等功能可以作为切面,在方法执行前后等特定时机切入执行。通过代理对象来调用目标方法,在代理对象的方法中织入切面逻辑。
  5. Spring Boot自动配置原理
    • Spring Boot通过条件注解来实现自动配置。
    • 它会根据项目中引入的依赖和配置信息,自动配置相应的组件和功能。例如,当引入了Spring Data JPA的依赖时,Spring Boot会自动配置JPA相关的组件,如EntityManagerFactory、JpaRepository等。通过在配置类上使用@Conditional注解,根据不同的条件决定是否创建相应的配置类和组件。比如@ConditionalOnClass用于判断类路径下是否存在某个类,@ConditionalOnProperty用于根据配置属性的值来决定是否配置某个组件。
  6. MyBatis的动态SQL
    • if:用于条件判断。例如:
    <select id="selectUsers" resultType="User">
        SELECT * FROM users
        <where>
            <if test="username!= null">
                AND username = #{username}
            </if>
            <if test="age!= null">
                AND age = #{age}
            </if>
        </where>
    </select>
    
    • where:自动处理where子句。当动态SQL中有多个条件时,如果第一个条件为真,会自动添加where关键字,如果前面的条件都为假,不会添加where关键字,避免SQL语法错误。
    • set:用于更新语句中处理set部分。例如:
    <update id="updateUser" parameterType="User">
        UPDATE users
        <set>
            <if test="username!= null">username = #{username},</if>
            <if test="age!= null">age = #{age}</if>
        </set>
        WHERE id = #{id}
    </update>
    
    会自动处理多余的逗号。
    • foreach:用于循环遍历集合。例如:
    <select id="selectUserInIds" resultType="User">
        SELECT * FROM users
        WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
    
    可以循环遍历集合中的元素,生成SQL的IN子句。
  7. Dubbo的集群容错策略
    • failover:失败重试。当调用服务失败时,会自动重试其他可用服务,默认重试次数为2次。
    • failfast:快速失败。当调用服务失败时,立即抛出异常,不再重试。
    • failsafe:失败安全。当调用服务失败时,不抛出异常,直接忽略,常用于写操作。
    • failback:失败自动恢复。当调用服务失败时,会记录失败请求,定时重试。
    • forking:并行调用多个服务取第一个成功结果。会同时调用多个服务,只要有一个成功就返回结果。
  8. RabbitMq的消息确认机制
    • 生产者确认机制
      • 事务模式:生产者发送消息前开启事务(channel.txSelect()),发送消息后如果没有异常则提交事务(channel.txCommit()),如果出现异常则回滚事务(channel.txRollback())。但这种方式性能较低。
      • Confirm模式:生产者发送消息后,通过调用channel.waitForConfirms()方法等待Broker的确认,如果确认成功则继续发送下一条消息,如果失败则进行相应处理。也可以通过设置回调函数,当消息确认结果返回时进行回调处理。例如:
      channel.confirmSelect();
      channel.addConfirmListener(new ConfirmListener() {
          @Override
          public void handleAck(long deliveryTag, boolean multiple) throws IOException {
              System.out.println("Message confirmed, deliveryTag: " + deliveryTag);
          }
      
          @Override
          public void handleNack(long deliveryTag, boolean multiple) throws IOException {
              System.out.println("Message not confirmed, deliveryTag: " + deliveryTag);
          }
      });
      
    • 消费者确认机制
      • 自动确认:消费者接收到消息后,自动确认消息已接收,消息会从队列中删除。通过设置autoAck为true来实现。例如:
      boolean autoAck = true;
      channel.basicConsume(queueName, autoAck, consumerTag, consumer);
      
      • 手动确认:消费者接收到消息后,不自动确认,需要调用channel.basicAck方法手动确认消息。可以设置是否批量确认等参数。例如:
      boolean autoAck = false;
      channel.basicConsume(queueName, autoAck, consumerTag, consumer);
      // 手动确认消息
      channel.basicAck(deliveryTag, false);
      
  9. xxl - job的核心组件
    • 调度中心:是xxl - job的核心,负责管理任务调度规则、触发调度运行、监控任务执行状态等。
    • 执行器:负责接收调度中心的调度请求并执行任务。可以是独立的应用,也可以是集成在其他项目中的模块。
    • 任务管理:在调度中心中对任务进行创建、编辑、删除等管理操作。可以定义任务的执行频率、参数等。
    • 日志管理:记录任务的执行日志,方便查看任务执行情况,排查问题。
  10. Redis的数据结构
  • 字符串(String):最基本的数据结构,能存储各种类型的数据,如整数、字符串等。可以进行原子性的自增、自减等操作。例如:
Jedis jedis = new Jedis("localhost");
jedis.set("key", "value");
String value = jedis.get("key");
jedis.incr("count");
  • 哈希(Hash):适合存储对象。例如存储一个用户对象,可以将用户的属性作为字段,属性值作为值。
jedis.hset("user:1", "name", "张三");
jedis.hset("user:1", "age", "20");
Map<String, String> userMap = jedis.hgetAll("user:1");
  • 列表(List):可以作为队列或栈使用。支持从列表两端添加和删除元素。例如:
jedis.lpush("mylist", "element1", "element2");
List<String> list = jedis.lrange("mylist", 0, -1);
  • 集合(Set):无序且唯一的数据结构。可以进行交集、并集、差集等操作。例如:
jedis.sadd("myset", "element1", "element2");
Set<String> set = jedis.smembers("myset");
  • 有序集合(Sorted Set):元素是有序的且唯一。通过分数来排序。例如:
jedis.zadd("mysortedset", 100, "element1");
jedis.zadd("mysortedset", 200, "element2");
Set<String> sortedSet = jedis.zrange("mysortedset", 0, -1);