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

114 阅读5分钟

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

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

面试官:不错,回答得很准确。那在多线程编程中,如何保证线程安全?

王铁牛:可以用 synchronized 关键字来同步代码块或者方法,也可以用 Lock 接口的实现类,比如 ReentrantLock。

面试官:很好。再问一个,JVM 的内存区域有哪些?

王铁牛:有程序计数器、虚拟机栈、本地方法栈、堆和方法区。

面试官:第一轮提问结束。接下来进入第二轮。说说线程池的几个核心参数及其作用。

王铁牛:线程池有 corePoolSize、maximumPoolSize、keepAliveTime、unit 和 workQueue 这几个参数。corePoolSize 是核心线程数,maximumPoolSize 是最大线程数,keepAliveTime 是线程存活时间,unit 是时间单位,workQueue 是任务队列。

面试官:那当提交的任务数大于 corePoolSize 时会怎样?

王铁牛:如果任务数大于 corePoolSize,就会把任务放到 workQueue 里,如果 workQueue 满了,就会创建新线程,直到线程数达到 maximumPoolSize,如果还装不下,就会根据拒绝策略处理任务。

面试官:第二轮结束。最后一轮,讲讲 HashMap 的底层数据结构和扩容机制。

王铁牛:HashMap 底层是数组加链表,后来又引入了红黑树。扩容机制就是当 HashMap 的容量达到阈值时,就会进行扩容,扩容后的容量是原来的两倍。

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

答案

  • Java 面向对象三大特性
    • 封装:将数据和操作数据的方法封装在一起,对外提供统一的访问接口,提高数据的安全性和程序的可维护性。比如一个类中的成员变量可以设置为私有,通过公共的 get 和 set 方法来访问和修改。
    • 继承:子类继承父类的属性和方法,实现代码复用。子类可以拥有父类的非私有成员,并且可以根据自身需求进行扩展。例如,一个 Animal 类有 eat 方法,Dog 类继承自 Animal 类,就可以直接使用 eat 方法。
    • 多态:同一个行为具有多个不同表现形式。分为编译时多态(方法重载)和运行时多态(方法重写)。运行时多态通过父类引用指向子类对象,调用方法时会根据对象的实际类型来决定调用哪个子类的方法。
  • 多线程保证线程安全的方法
    • synchronized:可以修饰代码块或者方法。修饰代码块时,被保护的代码块称为临界区,同一时刻只能有一个线程进入临界区执行代码。修饰方法时,整个方法为临界区。例如:
public class SynchronizedExample {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
}
- **Lock 接口**:如 ReentrantLock,它提供了比 synchronized 更灵活的锁控制。可以实现公平锁,可中断锁等特性。例如:
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
  • JVM 内存区域
    • 程序计数器:记录当前线程执行的字节码指令地址,是线程私有的。
    • 虚拟机栈:每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,也是线程私有的。
    • 本地方法栈:与虚拟机栈类似,用于执行本地方法,同样是线程私有的。
    • :是 JVM 中最大的内存区域,被所有线程共享,用于存放对象实例。
    • 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 线程池核心参数及其作用
    • corePoolSize:核心线程数,当提交的任务数小于 corePoolSize 时,线程池会创建新线程来执行任务。
    • maximumPoolSize:最大线程数,当提交的任务数大于 corePoolSize 且 workQueue 已满时,会创建新线程直到线程数达到 maximumPoolSize。
    • keepAliveTime:线程存活时间,当线程数大于 corePoolSize 时,多余的线程在空闲一段时间后会被销毁,这个空闲时间就是 keepAliveTime。
    • unit:时间单位,用于指定 keepAliveTime 的时间单位。
    • workQueue:任务队列,当提交的任务数大于 corePoolSize 时,任务会被放入 workQueue 中等待执行。
  • HashMap 底层数据结构和扩容机制
    • 底层数据结构:JDK1.8 之前是数组 + 链表,JDK1.8 之后是数组 + 链表 + 红黑树。当链表长度大于 8 且数组容量大于 64 时,链表会转换为红黑树,以提高查询效率。
    • 扩容机制:当 HashMap 的容量达到阈值(容量 * 负载因子,默认负载因子为 0.75)时,就会进行扩容。扩容后的容量是原来的两倍,扩容时会重新计算每个元素的位置,并将其放入新的数组中。例如,原来容量为 16,当元素个数达到 16 * 0.75 = 12 时就会触发扩容,扩容后容量变为 32。