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

23 阅读9分钟

面试官:请简要介绍一下 Java 核心知识中的面向对象编程三大特性。

王铁牛:嗯……这个嘛,是封装、继承和多态。封装就是把数据和操作数据的方法封装在一起;继承就是子类继承父类的属性和方法;多态就是同一个行为具有多个不同表现形式。

面试官:不错,回答得很准确。那再问你,在多线程环境下,如何保证数据的一致性?

王铁牛:可以用 synchronized 关键字呀,给共享数据加上锁,这样同一时间只有一个线程能访问,就能保证数据一致性啦。

面试官:很好。接下来问几个关于 JUC 的问题。讲讲 CountDownLatch 的作用和用法。

王铁牛:CountDownLatch 就是用来等待一组操作完成嘛。比如有多个线程要完成一些任务,等所有任务都完成了,主线程再继续执行。用法就是先创建一个 CountDownLatch 对象,指定计数值,然后在需要等待的地方调用 await 方法,在任务完成的地方调用 countDown 方法减少计数值。

第一轮结束

面试官:进入第二轮。说说 JVM 中的垃圾回收算法有哪些?

王铁牛:嗯……有标记清除、标记整理、复制算法。标记清除就是先标记出要回收的对象,然后再清除;标记整理是标记后把存活对象整理到一端;复制算法是把内存分成两块,每次只用一块,用完了把存活对象复制到另一块。

面试官:那 CMS 垃圾回收器的过程是怎样的?

王铁牛:这个……好像是初始标记、并发标记、重新标记、并发清除。

面试官:不太准确。CMS 垃圾回收器的过程是初始标记,暂停所有的其他线程,标记出 GC Roots 能直接关联到的对象;并发标记,和用户线程一起并发执行,标记出全部对象;重新标记,重新标记在并发标记期间发生变化的对象,暂停用户线程;并发清除,清除掉标记阶段判断为可回收的对象,和用户线程一起并发执行。再问你,多线程中线程池的核心参数有哪些?

王铁牛:有 corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。

第二轮结束

面试官:最后一轮。讲讲 HashMap 的底层数据结构。

王铁牛:就是数组加链表嘛,后来链表长度超过 8 会转成红黑树。

面试官:ArrayList 的扩容机制是怎样的?

王铁牛:扩容就是原来容量翻倍,然后把旧数据复制到新数组。

面试官:Spring 框架中 IOC 和 AOP 的概念是什么?

王铁牛:IOC 就是控制反转,把对象的创建和依赖注入交给 Spring 容器;AOP 就是面向切面编程,在不修改原有代码的基础上增强功能。

面试结束

面试官:今天的面试就到这里,回去等通知吧。

答案

  1. Java 面向对象编程三大特性
    • 封装:把数据和操作数据的方法封装在一起,对外提供统一的访问接口。这样可以隐藏内部实现细节,提高代码的安全性和可维护性。例如,一个类中有私有成员变量,通过公有的 get 和 set 方法来访问和修改这些变量。
    • 继承:子类继承父类的属性和方法。子类可以复用父类的代码,实现代码的复用和扩展。比如,有一个父类 Animal,子类 Dog 和 Cat 可以继承 Animal 的属性(如 name、age)和方法(如 eat),同时子类可以有自己特有的属性和方法。
    • 多态:同一个行为具有多个不同表现形式。可以通过方法重写和接口实现来体现。例如,有一个接口 Shape,子类 Circle 和 Rectangle 实现这个接口,当调用 draw 方法时,根据对象的实际类型(Circle 或 Rectangle)来执行不同的绘制逻辑。
  2. 多线程环境下保证数据一致性
    • synchronized 关键字:它可以给共享数据加上锁。当一个线程访问被 synchronized 修饰的代码块或方法时,会先获取锁。如果锁已经被其他线程持有,那么该线程会进入等待状态,直到锁被释放。这样同一时间只有一个线程能访问共享数据,从而保证数据一致性。比如在一个银行账户类中,对存款和取款方法加上 synchronized 锁,防止多个线程同时操作账户余额导致数据错误。
  3. CountDownLatch 的作用和用法
    • 作用:用来等待一组操作完成。适用于多个线程需要协同完成一些任务,等所有任务都完成后,主线程再继续执行后续操作的场景。
    • 用法
      • 首先创建一个 CountDownLatch 对象,构造函数传入计数值,比如 CountDownLatch latch = new CountDownLatch(5); 这里的 5 表示有 5 个操作需要完成。
      • 在需要等待的地方调用 await 方法,如 latch.await(); 此时线程会阻塞,直到计数值变为 0。
      • 在每个任务完成的地方调用 countDown 方法,如 latch.countDown(); 每调用一次 countDown 方法,计数值就减 1,当计数值减到 0 时,等待的线程会被唤醒继续执行。
  4. JVM 中的垃圾回收算法
    • 标记清除算法
      • 过程:先标记出所有需要回收的对象,然后统一回收这些对象所占用的内存空间。
      • 缺点:会产生大量不连续的内存碎片,导致后续分配大对象时可能无法找到足够连续的内存空间。
    • 标记整理算法
      • 过程:先标记出需要回收的对象,然后将存活的对象向内存空间的一端移动,最后清理掉边界以外的内存。
      • 优点:解决了标记清除算法产生内存碎片的问题。
    • 复制算法
      • 过程:将内存空间分成两块大小相等的区域,每次只使用其中一块。当这一块内存使用完后,将存活的对象复制到另一块空闲区域,然后清除原来使用的区域。
      • 优点:实现简单,运行效率高;缺点:浪费了一半的内存空间,适用于对象存活率较低的场景。
  5. CMS 垃圾回收器的过程
    • 初始标记:暂停所有的其他线程,标记出 GC Roots 能直接关联到的对象。这一步会产生短暂的停顿。
    • 并发标记:和用户线程一起并发执行,标记出全部对象。在这个阶段,用户线程可以正常运行,不会产生停顿。
    • 重新标记:重新标记在并发标记期间发生变化的对象,暂停用户线程。这一步是为了确保标记的准确性,虽然停顿时间比初始标记长,但比完整的标记清除算法停顿时间短。
    • 并发清除:清除掉标记阶段判断为可回收的对象,和用户线程一起并发执行。
  6. 线程池的核心参数
    • corePoolSize:线程池的核心线程数。当提交的任务数小于 corePoolSize 时,线程池会创建新线程来执行任务。
    • maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于 corePoolSize 且任务队列已满时,会创建新线程来执行任务,直到线程数达到 maximumPoolSize。
    • keepAliveTime:线程池中非核心线程的存活时间。当线程数大于 corePoolSize 时,超过 keepAliveTime 时间的非核心线程会被销毁。
    • unit:keepAliveTime 的时间单位。
    • workQueue:任务队列,用于存放提交的任务。当提交的任务数大于 corePoolSize 时,会将任务放入任务队列中。
    • threadFactory:线程工厂,用于创建线程,可自定义线程的名称、优先级等属性。
    • handler:任务拒绝策略,当线程数达到 maximumPoolSize 且任务队列已满时,会调用该策略来处理新提交的任务。常见的策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程处理任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  7. HashMap 的底层数据结构
    • HashMap 底层是数组 + 链表 + 红黑树的数据结构。
    • 初始时,HashMap 有一个默认大小的数组。当插入键值对时,会通过计算键的哈希值,然后根据哈希值对数组长度取模得到数组下标,将键值对存储在该下标对应的位置。
    • 如果该位置为空,直接插入新的键值对。
    • 如果该位置不为空,且键相同,则更新值。
    • 如果该位置不为空,且键不同,则形成链表。当链表长度超过 8 时,链表会转成红黑树,以提高查找效率。
  8. ArrayList 的扩容机制
    • ArrayList 初始容量为 10。
    • 当添加元素导致容量不足时,会进行扩容。扩容时,新的容量是原来容量的 1.5 倍(即原来容量翻倍)。
    • 然后创建一个新的更大容量的数组,将原来数组中的元素复制到新数组中,再将新元素添加到新数组中。
  9. Spring 框架中 IOC 和 AOP 的概念
    • IOC(控制反转):把对象的创建和依赖注入交给 Spring 容器。传统方式下,对象之间的依赖关系由开发者在代码中主动创建和管理;而在 IOC 容器中,容器负责创建对象,并根据配置将对象之间的依赖关系注入到需要的地方。例如,一个 Service 类依赖于一个 Dao 类,在 IOC 容器中,容器会自动创建 Dao 对象并注入到 Service 类中,开发者只需要从容器中获取 Service 类使用即可,无需关心其内部依赖的创建。
    • AOP(面向切面编程):在不修改原有代码的基础上增强功能。它将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,通过动态代理等方式织入到目标对象的代码中。比如在一个用户注册业务方法前后添加日志记录,使用 AOP 可以在不修改注册业务方法代码的前提下实现日志记录功能,提高代码的可维护性和复用性。