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

33 阅读7分钟

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

王铁牛:嗯……这个我知道,是封装、继承和多态。

面试官:不错,回答正确。那请说一下 HashMap 的底层数据结构。

王铁牛:HashMap 底层是数组和链表,在 JDK 1.8 之后还引入了红黑树。

面试官:很好。现在问你一个多线程的问题,如何实现线程间的同步?

王铁牛:可以用 synchronized 关键字或者 Lock 接口来实现。

第一轮结束。

面试官:接下来问你关于 JVM 的问题,什么是类加载器?

王铁牛:类加载器就是负责加载类文件的,有启动类加载器、扩展类加载器、应用程序类加载器等。

面试官:那简述一下 Spring 的核心特性。

王铁牛:Spring 可以实现依赖注入、面向切面编程啥的。

面试官:再问一个,说说 MyBatis 的优点。

王铁牛:呃……它很灵活,能自己写 SQL。

第二轮结束。

面试官:最后一轮了,说一下 JUC 包下的常用类。

王铁牛:有 CountDownLatch、CyclicBarrier 啥的。

面试官:谈谈线程池的参数及作用。

王铁牛:这个……大概就是 corePoolSize、maximumPoolSize 那些吧。

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

答案:

  1. Java 面向对象三大特性
    • 封装:将对象的属性和行为包装起来,对外提供统一的访问接口,提高了代码的安全性和可维护性。比如一个类中有一些私有属性,通过公有的 get 和 set 方法来访问和修改这些属性。
    • 继承:子类继承父类的属性和方法,实现代码复用。例如,一个 Animal 类有一些通用的属性和方法,Dog 类可以继承 Animal 类,从而拥有 Animal 类的特性,同时还可以有自己特有的属性和方法。
    • 多态:同一个行为具有多个不同表现形式或形态的能力。比如一个父类类型的引用可以指向子类的对象,当调用这个引用的方法时,会根据实际指向的子类对象来执行相应的方法。
  2. HashMap 底层数据结构
    • 在 JDK 1.8 之前,HashMap 底层是数组 + 链表结构。数组中的每个元素是一个链表节点,当发生哈希冲突时,新元素会添加到链表的末尾。
    • 在 JDK 1.8 之后,当链表长度大于 8 且数组长度大于 64 时,链表会转换为红黑树,以提高查询效率。红黑树是一种自平衡二叉查找树,它能在 O(log n) 的时间复杂度内进行插入、删除和查找操作。
  3. 线程间同步的实现方式
    • synchronized 关键字:可以修饰代码块或方法。当一个线程访问被 synchronized 修饰的代码块或方法时,它会首先获取对象的锁。如果锁已经被其他线程持有,那么该线程会被阻塞,直到锁被释放。例如,在一个类中有一个被 synchronized 修饰的方法,多个线程同时调用这个方法时,同一时间只有一个线程能进入该方法执行,其他线程需要等待。
    • Lock 接口:通过 Lock 接口的实现类如 ReentrantLock 来实现线程同步。与 synchronized 不同,Lock 提供了更灵活的锁控制。比如可以通过 tryLock 方法尝试获取锁,获取不到不会一直阻塞;还可以通过 lockInterruptibly 方法在获取锁的过程中响应中断等。例如使用 ReentrantLock 时,可以这样写:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock();
}
  1. 类加载器
    • 启动类加载器:负责加载 Java 核心类库,如 rt.jar 中的类,是由 C++ 实现的,无法被 Java 程序直接引用。
    • 扩展类加载器:加载 Java 的扩展类库,默认加载路径为 jre/lib/ext 目录下的类。
    • 应用程序类加载器:负责加载应用程序的类路径下的类,是最常用的类加载器,也被称为系统类加载器。
    • 除了这三个主要的类加载器,还有自定义类加载器,开发者可以通过继承 ClassLoader 类来实现自己的类加载器,用于加载特定来源的类文件。
  2. Spring 的核心特性
    • 依赖注入(Dependency Injection):通过控制反转(IoC)容器,将对象之间的依赖关系由程序主动创建改为由容器注入,降低了对象之间的耦合度。例如,一个 Service 类依赖于一个 Dao 类,在 Spring 中,可以通过配置文件或注解,让 Spring 容器将合适的 Dao 对象注入到 Service 类中。
    • 面向切面编程(Aspect - Oriented Programming,AOP):允许将一些横切关注点(如日志记录、事务管理等)与业务逻辑分离,通过切面(Aspect)织入到目标对象的方法执行过程中。比如在多个业务方法中都需要进行日志记录,使用 AOP 可以将日志记录的代码统一放到一个切面中,而不需要在每个业务方法中重复编写。
    • IoC 容器:负责创建、管理和装配对象,管理对象之间的依赖关系,使得对象的创建和使用更加灵活和可维护。
  3. MyBatis 的优点
    • SQL 编写灵活:开发人员可以直接编写 SQL 语句,能够根据具体业务需求进行定制化的 SQL 编写,而不像一些框架会对 SQL 有较多的限制。比如复杂的多表联合查询、分组统计等操作都可以方便地实现。
    • 轻量级:MyBatis 本身对数据库的侵入性较小,只专注于 SQL 映射和执行,相比于一些重量级的框架,性能开销较小,适合对性能要求较高的场景。
    • 与多种数据库兼容:支持多种主流数据库,如 MySQL、Oracle、SQL Server 等,能够方便地在不同数据库环境之间切换使用。
  4. JUC 包下的常用类
    • CountDownLatch:允许一个或多个线程等待其他线程完成操作。例如,有一个主线程需要等待多个子线程都完成某项任务后再继续执行,可以使用 CountDownLatch。主线程调用 await 方法等待,子线程完成任务后调用 countDown 方法,当 CountDownLatch 的计数器减为 0 时,主线程的 await 方法返回继续执行。
    • CyclicBarrier:它允许一组线程互相等待,直到到达某个公共屏障点。与 CountDownLatch 不同的是,CyclicBarrier 的计数器可以循环使用。比如在一个多线程协作完成某项任务的场景中,多个线程都需要完成各自的一部分工作后,在某个点进行汇总处理,就可以使用 CyclicBarrier。当所有线程都调用 await 方法到达屏障点时,所有线程会同时继续执行后续的代码。
  5. 线程池的参数及作用
    • corePoolSize:线程池的核心线程数。当提交的任务数小于 corePoolSize 时,线程池会创建新的线程来执行任务。
    • maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于 corePoolSize 时,若任务队列已满,则会创建新的线程,直到线程数达到 maximumPoolSize。
    • keepAliveTime:线程池中非核心线程的存活时间。当线程数大于 corePoolSize 且线程空闲时间超过 keepAliveTime 时,非核心线程会被销毁。
    • unit:keepAliveTime 的时间单位。
    • BlockingQueue:任务队列,用于存放提交的任务。常用的有 ArrayBlockingQueue、LinkedBlockingQueue 等。当提交的任务数大于 corePoolSize 时,任务会被放入任务队列中等待执行。
    • ThreadFactory:线程工厂,用于创建线程,可自定义线程的名称、优先级等属性。
    • RejectedExecutionHandler:拒绝策略,当线程池的线程数达到 maximumPoolSize 且任务队列已满时,会调用该策略来处理新提交的任务。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(调用者线程执行任务)、DiscardPolicy(丢弃新提交的任务)、DiscardOldestPolicy(丢弃队列中最老的任务)等。