面试官:请简要介绍一下 Java 核心知识中,类和对象的关系。
王铁牛:类是对象的模板,对象是类的实例。就像模具和用模具做出来的东西一样,类定义了对象的属性和行为,对象则是具体拥有这些属性和行为的实例。
面试官:不错,回答得很清晰。那说说 JUC 包下常用的几个类及其作用。
王铁牛:比如说 CountDownLatch 吧,它可以让一个线程等待其他线程完成一系列操作后再继续执行。还有 CyclicBarrier,能让一组线程互相等待,直到到达某个公共屏障点。
面试官:嗯,理解得挺到位。第一轮面试到此结束,回家等通知吧。
第二轮:
面试官:谈谈 JVM 的内存结构以及各个区域的功能。
王铁牛:JVM 内存结构包括堆、栈、方法区等。堆是存放对象实例的地方,栈主要存储局部变量等,方法区存放类信息、常量等。
面试官:那多线程中,如何实现线程安全?
王铁牛:可以用 synchronized 关键字,还有 Lock 接口及其实现类。
面试官:线程池的核心参数有哪些,分别有什么作用?
王铁牛:核心参数有 corePoolSize、maximumPoolSize、keepAliveTime 等。corePoolSize 是核心线程数,maximumPoolSize 是最大线程数,keepAliveTime 是线程存活时间。
面试官:第二轮面试结束,回家等通知。
第三轮:
面试官:讲讲 HashMap 的底层实现原理。
王铁牛:HashMap 底层是数组加链表再加红黑树。当链表长度超过一定阈值,就会转换为红黑树。
面试官:ArrayList 是如何实现动态扩容的?
王铁牛:当元素个数超过数组容量时,会创建一个新的更大的数组,然后将原数组的元素复制过去。
面试官:说说 Spring 框架中依赖注入的几种方式。
王铁牛:有构造器注入、setter 注入等。
面试官:第三轮面试结束,回家等通知。
答案:
- 类和对象的关系:类是对象的模板,定义了对象的属性和行为,对象是类的实例,根据类来创建并拥有类所定义的属性和行为。
- JUC 包下常用类及其作用:
- CountDownLatch:允许一个或多个线程等待其他线程完成一系列操作后再继续执行。通过构造函数传入一个计数值,调用 countDown 方法使计数值减 1,当计数值为 0 时,等待的线程被唤醒继续执行。
- CyclicBarrier:让一组线程互相等待,直到到达某个公共屏障点。构造函数传入 parties(参与线程数)和一个可选的屏障动作。当每个线程调用 await 方法时,会等待直到所有线程都到达屏障点,然后可以一起继续执行后续操作,并且可以循环使用。
- JVM 内存结构及各区域功能:
- 堆:存放对象实例,是 JVM 所管理的内存中最大的一块区域,被所有线程共享。
- 栈:主要存储局部变量、方法调用的上下文等,每个线程都有自己独立的栈空间。
- 方法区:存放类信息、常量、静态变量等数据,被所有线程共享。
- 多线程实现线程安全的方法:
- synchronized 关键字:可以修饰代码块或方法,保证同一时刻只能有一个线程访问被修饰的代码块或方法,从而实现线程安全。
- Lock 接口及其实现类:如 ReentrantLock,提供了比 synchronized 更灵活的锁控制,例如可中断锁、公平锁等功能。可以通过 lock 方法获取锁,unlock 方法释放锁。
- 线程池核心参数及其作用:
- corePoolSize:核心线程数,当提交的任务数小于 corePoolSize 时,线程池会创建新线程来执行任务。
- maximumPoolSize:最大线程数,当提交的任务数大于 corePoolSize 且任务队列已满时,会创建新线程执行任务,但线程数不能超过 maximumPoolSize。
- keepAliveTime:线程存活时间,当线程数大于 corePoolSize 时,多余的线程在空闲一段时间(由 keepAliveTime 定义)后会被销毁。
- HashMap 底层实现原理:
- 底层是数组加链表再加红黑树。初始时创建一个一定大小的数组,每个数组元素是一个链表节点。
- 当向 HashMap 中插入键值对时,通过 key 的哈希值计算出在数组中的索引位置。
- 如果该位置为空,则直接插入新节点;如果不为空,则遍历链表(或红黑树),若找到相同 key 的节点,则更新其 value;若未找到,则在链表末尾插入新节点。
- 当链表长度超过一定阈值(默认为 8)且数组容量大于等于 64 时,链表会转换为红黑树,以提高查询效率。
- ArrayList 实现动态扩容的方式:
- 当 ArrayList 中的元素个数超过当前数组容量时,会创建一个新的更大的数组,新数组容量通常是原数组容量的 1.5 倍(如果原数组容量小于 64)或原数组容量的 2 倍(如果原数组容量大于等于 64)。
- 然后将原数组中的元素逐一复制到新数组中,最后抛弃原数组,使得 ArrayList 可以继续添加新元素。
- Spring 框架中依赖注入的方式:
- 构造器注入:通过构造函数来注入依赖对象,优点是注入的依赖在对象创建时就已经确定,并且是不可变的。
- setter 注入:通过 setter 方法来注入依赖对象,优点是更加灵活,可以在对象创建后再设置依赖,并且可以重新设置依赖。