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

108 阅读8分钟

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

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

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

王铁牛:这个我知道!可以使用synchronized关键字,它可以保证同一时刻只有一个线程能访问被它修饰的代码块或方法。还可以用Lock接口,它提供了比synchronized更灵活的锁控制。

面试官:很好。接下来问几个关于JUC的问题。什么是线程池?有哪些常见的线程池类型?

王铁牛:线程池就是预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务。常见的线程池类型有FixedThreadPool、CachedThreadPool、ScheduledThreadPool和SingleThreadExecutor。

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

面试官:谈谈JVM的内存结构。

王铁牛:JVM内存结构包括堆、栈、方法区、程序计数器、本地方法栈。堆是存放对象实例的地方;栈主要存放局部变量表、操作数栈等;方法区存储类信息、常量、静态变量等;程序计数器记录当前线程执行的字节码指令地址;本地方法栈用于执行本地方法。

面试官:那垃圾回收机制是怎样的?

王铁牛:垃圾回收机制就是自动回收不再使用的对象所占用的内存。常见的垃圾回收算法有标记清除算法、标记整理算法、复制算法、分代收集算法等。

面试官:在多线程中,如何实现线程间的通信?

王铁牛:可以使用Object类的wait()、notify()和notifyAll()方法。wait()方法让当前线程等待,直到其他线程调用该对象的notify()或notifyAll()方法。

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

面试官:说说HashMap的底层实现原理。

王铁牛:HashMap底层是基于数组和链表实现的。当插入元素时,先通过哈希函数计算出元素的哈希值,然后根据哈希值找到对应的数组位置。如果该位置为空,就直接插入;如果不为空,就形成链表。当链表长度超过一定阈值时,会将链表转换为红黑树,以提高查询效率。

面试官:ArrayList的优缺点是什么?

王铁牛:优点是随机访问速度快,因为它内部是通过数组实现的。缺点是插入和删除操作效率低,因为需要移动元素。

面试官:Spring框架的核心特性有哪些?

王铁牛:Spring框架的核心特性有依赖注入、面向切面编程、IoC容器、事务管理等。

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

答案:

  • 面向对象的三大特性
    • 封装:将数据和操作数据的方法封装在一起,对外提供统一的接口,隐藏内部实现细节。这样可以提高代码的安全性和可维护性,比如一个类中的属性可以通过private修饰,然后提供public的getter和setter方法来访问和修改。
    • 继承:子类继承父类的属性和方法,实现代码的复用。子类可以扩展和重写父类的方法,比如一个父类有某个方法,子类可以根据自身需求重写该方法以实现不同的功能。
    • 多态:同一个行为具有多个不同表现形式。分为编译时多态(方法重载)和运行时多态(方法重写)。运行时多态通过父类引用指向子类对象,在调用方法时根据对象的实际类型来决定调用哪个子类的方法。
  • 保证线程安全的方法
    • synchronized关键字:可以修饰代码块或方法。当一个线程访问被synchronized修饰的代码块或方法时,会先获取对象的锁。如果锁被其他线程占用,该线程会进入等待状态,直到锁被释放。例如:
public class SynchronizedExample {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
}
- **Lock接口**:提供了更灵活的锁控制。如ReentrantLock类实现了Lock接口。可以使用lock()方法获取锁,unlock()方法释放锁,还可以使用tryLock()方法尝试获取锁,避免死锁。例如:
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();
        }
    }
}
  • 线程池:预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,避免了频繁创建和销毁线程的开销。
    • 常见线程池类型
      • FixedThreadPool:创建一个固定大小的线程池,线程数量一旦创建就不会改变。适用于需要处理大量固定任务的场景。
      • CachedThreadPool:线程池的线程数量会根据需要动态创建和销毁,适合处理大量短时间任务。
      • ScheduledThreadPool:可以定时或周期性执行任务。
      • SingleThreadExecutor:只有一个线程的线程池,保证任务按顺序执行。
  • JVM内存结构
    • :存放对象实例,是垃圾回收的主要区域。
    • :主要存放局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用时会创建一个栈帧,栈帧中包含局部变量表等。
    • 方法区:存储类信息、常量、静态变量等。在Java 8及以后,方法区被元空间取代。
    • 程序计数器:记录当前线程执行的字节码指令地址,是线程私有的。
    • 本地方法栈:用于执行本地方法。
  • 垃圾回收机制
    • 标记清除算法:先标记出所有需要回收的对象,然后统一回收这些对象所占用的内存。这种算法会产生大量不连续的内存碎片。
    • 标记整理算法:先标记出需要回收的对象,然后将存活的对象向一端移动,最后清理掉边界以外的内存。
    • 复制算法:将内存分为两块,每次只使用其中一块。当这一块内存使用完后,将存活的对象复制到另一块内存,然后清理原来的内存。适用于对象存活率低的场景。
    • 分代收集算法:根据对象的存活周期将堆内存分为新生代、老年代等不同区域,针对不同区域采用不同的垃圾回收算法。新生代对象存活率低,适合用复制算法;老年代对象存活率高,适合用标记清除或标记整理算法。
  • 线程间通信
    • 使用Object类的wait()、notify()和notifyAll()方法。
    • wait()方法:让当前线程等待,直到其他线程调用该对象的notify()或notifyAll()方法。调用wait()方法后,当前线程会释放对象的锁。例如:
public class ThreadCommunication {
    private Object lock = new Object();
    public void method() {
        synchronized (lock) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
- **notify()方法**:唤醒在此对象监视器上等待的单个线程。
- **notifyAll()方法**:唤醒在此对象监视器上等待的所有线程。
  • HashMap底层实现原理
    • 基于数组和链表实现,在Java 8及以后引入了红黑树。
    • 插入元素时,先通过哈希函数计算出元素的哈希值,然后根据哈希值找到对应的数组位置。
    • 如果该位置为空,就直接插入新元素。
    • 如果不为空,就将新元素插入到链表的头部(Java 8之前)或根据链表长度和树化阈值决定是否将链表转换为红黑树(Java 8及以后)。当链表长度超过一定阈值(默认8)且数组容量大于等于64时,链表会转换为红黑树,以提高查询效率。
  • ArrayList的优缺点
    • 优点:随机访问速度快,因为它内部是通过数组实现的,通过下标可以直接定位到元素。
    • 缺点:插入和删除操作效率低,因为插入和删除元素时需要移动其他元素。例如插入元素时,从插入位置开始,后面的元素都要向后移动一位。
  • Spring框架的核心特性
    • 依赖注入:通过控制反转(IoC)实现,将对象的创建和依赖关系的管理交给Spring容器,而不是在对象内部自行创建和管理依赖对象。比如一个类需要依赖另一个类,Spring可以通过配置将依赖的类注入到该类中。
    • 面向切面编程(AOP):可以在不修改业务逻辑的前提下,对业务逻辑进行增强。例如可以通过AOP实现日志记录、事务管理等功能。
    • IoC容器:负责创建、配置和管理对象之间的依赖关系,是Spring框架的核心。
    • 事务管理:提供了声明式和编程式事务管理方式。声明式事务管理通过注解或XML配置来管理事务,编程式事务管理则通过代码来控制事务的开启、提交和回滚。