《互联网大厂Java面试大挑战:核心知识与实战难题》

36 阅读7分钟

面试官:第一轮面试开始。首先,讲讲 Java 中的多线程有哪些实现方式?

王铁牛:多线程实现方式嘛,有继承 Thread 类,还有实现 Runnable 接口。

面试官:嗯,回答得不错。那线程池的核心参数有哪些?

王铁牛:这个……好像有 corePoolSize、maximumPoolSize 这些吧。

面试官:还有呢?

王铁牛:哎呀,不太记得全了。

面试官:最后一个问题,简述一下 HashMap 的底层实现原理。

王铁牛:就是数组加链表,好像还有红黑树什么的,不太清楚细节了。

面试官:好,第一轮面试结束。

换行

面试官:第二轮面试。说说 JVM 的内存模型都有哪些部分?

王铁牛:这个……有堆、栈、方法区吧。

面试官:那对象在 JVM 中是如何分配内存的?

王铁牛:呃……不太明白您的意思,大概是在堆里分配吧。

面试官:再问一个,JUC 包下的 CountDownLatch 是怎么用的?

王铁牛:这个……没用过,不太会回答。

面试官:第二轮面试结束。

换行

面试官:第三轮面试。讲讲 Spring 的核心特性有哪些?

王铁牛:有依赖注入、面向切面编程吧。

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

王铁牛:这个……不太清楚,没深入研究过。

面试官:最后一个问题,MyBatis 的动态 SQL 有哪些?

王铁牛:好像有 if、foreach 这些,具体也不太熟。

面试官:好,面试结束了。回家等通知吧。

答案:

  1. Java 多线程实现方式
    • 继承 Thread 类:定义一个类继承 Thread 类,重写 run 方法,该类的实例就是一个线程对象,启动线程调用 start 方法,会执行 run 方法中的代码。
    • 实现 Runnable 接口:定义一个类实现 Runnable 接口,实现 run 方法,然后通过 Thread 类的构造方法将该实现类的实例作为参数传入,创建 Thread 对象,调用 start 方法启动线程,线程会执行 Runnable 实现类中的 run 方法。
  2. 线程池核心参数
    • corePoolSize:线程池的核心线程数。当提交的任务数小于 corePoolSize 时,线程池会创建新线程来执行任务。
    • maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于 corePoolSize 且队列已满时,会创建新线程直到线程数达到 maximumPoolSize。
    • keepAliveTime:线程池中的线程在空闲时的存活时间。当线程空闲时间超过 keepAliveTime 时,非核心线程会被销毁。
    • unit:keepAliveTime 的时间单位。
    • workQueue:线程池中的任务队列,用于存放提交的任务。
    • threadFactory:线程池创建线程的工厂,用于定义线程的名称、优先级等属性。
    • handler:当线程池的线程数达到 maximumPoolSize 且队列已满时,用于处理新提交任务的拒绝策略。
  3. HashMap 底层实现原理
    • 数组:HashMap 底层是一个数组,数组的每个元素是一个链表节点(JDK 1.8 后链表长度大于 8 会转换为红黑树)。数组的大小是 2 的幂次方。
    • 哈希值计算:通过 key 的 hashCode 方法计算哈希值,然后通过哈希值与数组长度取模确定元素在数组中的位置。
    • 链表和红黑树:如果不同的 key 计算出相同的哈希值,就会在数组的同一位置形成链表或红黑树。插入元素时,如果链表长度小于 8,新元素会插入到链表头部;如果链表长度大于等于 8 且数组长度大于等于 64,链表会转换为红黑树。查找元素时,先通过哈希值找到数组位置,然后在链表或红黑树中查找。
  4. JVM 内存模型部分
    • :是 JVM 中最大的一块内存区域,用于存放对象实例和数组。堆被分为新生代、老年代和永久代(JDK 1.8 后为元空间)。
    • :每个线程都有自己的栈,用于存放局部变量、方法调用等。栈内存是线程私有的。
    • 方法区:用于存放类信息、常量、静态变量等。JDK 1.8 后方法区的实现为元空间,它不再占用堆内存,而是使用本地内存。
  5. 对象在 JVM 中分配内存过程
    • 当创建一个对象时,首先会在 Eden 区分配内存。
    • 如果 Eden 区空间足够,对象会直接在 Eden 区创建。
    • 当 Eden 区空间不足时,会触发 Minor GC(新生代垃圾回收),将 Eden 区和 Survivor 区中存活的对象复制到另一个 Survivor 区,未存活的对象被回收。
    • 如果对象在多次 Minor GC 后仍然存活,会被晋升到老年代。
    • 当老年代空间不足时,会触发 Full GC(全堆垃圾回收),回收整个堆中的垃圾对象。
  6. JUC 包下 CountDownLatch 使用方法
    • CountDownLatch 是一个同步辅助类,它允许一个或多个线程等待其他线程完成一组操作之后再继续执行。
    • 例如,有多个线程需要完成一些初始化工作,然后主线程需要等待所有初始化工作完成后再继续执行后续任务。可以创建一个 CountDownLatch 对象,并传入初始化工作的线程数量作为参数。每个初始化线程完成工作后,调用 CountDownLatch 的 countDown 方法,将计数器减 1。主线程调用 CountDownLatch 的 await 方法等待,直到计数器变为 0,此时主线程会继续执行。
  7. Spring 核心特性
    • 依赖注入(DI):通过控制反转(IoC)容器,将对象之间的依赖关系由程序代码直接控制改为由容器来管理。例如,一个类需要依赖另一个类的实例,可以通过配置文件或注解,让容器创建并注入依赖的对象。
    • 面向切面编程(AOP):将横切关注点(如日志记录、事务管理等)与业务逻辑分离。通过 AOP,可以在不修改业务代码的情况下,为业务方法添加额外的功能。例如,可以使用切面来实现方法执行前的日志记录,方法执行后的事务提交等。
    • IoC 容器:负责创建、管理和装配对象,实现了对象之间的解耦。开发人员只需要关注业务逻辑,对象的创建和依赖关系由容器处理。
  8. Spring Boot 自动配置原理
    • Spring Boot 利用了 Spring 的条件化配置功能。它通过 @Conditional 注解来实现自动配置。
    • 当应用启动时,Spring Boot 会遍历所有的自动配置类。这些自动配置类会根据当前应用的环境、类路径下的依赖等条件来决定是否生效。
    • 例如,如果类路径下存在某个数据库驱动的依赖,那么与该数据库相关的自动配置类就会生效,它会自动配置数据源、事务管理器等相关的 bean,开发者不需要手动进行复杂的配置。
    • Spring Boot 通过大量的自动配置类,为常见的场景提供了默认的配置,极大地简化了开发过程。
  9. MyBatis 的动态 SQL
    • if:根据条件判断动态拼接 SQL 语句。例如:
    <select id="findUserById" parameterType="int" resultType="User">
        SELECT * FROM user
        WHERE 1 = 1
        <if test="id!= null">
            AND id = #{id}
        </if>
    </select>
    
    • foreach:用于循环拼接 SQL 语句,通常用于处理集合类型的参数。例如:
    <select id="findUsersByIds" parameterType="list" resultType="User">
        SELECT * FROM user
        WHERE id IN
        <foreach collection="list" item="item" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>
    
    • 还有其他动态 SQL 标签,如 where、set 等,where 标签可以自动处理 SQL 语句中多余的 AND 或 OR 条件,set 标签可以自动处理更新语句中多余的逗号。