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

19 阅读11分钟

在互联网大厂的一间明亮面试室内,严肃的面试官正对面坐着紧张的求职者王铁牛。一场关于 Java 核心知识的面试即将展开。

第一轮面试开始 面试官:首先问你几个基础问题。Java 中多态的实现方式有哪些? 王铁牛:多态的实现方式主要有方法重载和方法重写。方法重载是在一个类中,有多个方法名相同,但参数列表不同的情况;方法重写是子类对父类中允许访问的方法进行重新编写。 面试官:不错,回答得很准确。那说说 HashMap 的底层数据结构是什么? 王铁牛:HashMap 的底层数据结构在 JDK 1.8 之前是数组加链表,JDK 1.8 之后是数组加链表加红黑树。当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树,以提高查找效率。 面试官:回答得很好。那 ArrayList 和 LinkedList 的区别是什么? 王铁牛:ArrayList 是基于动态数组实现的,它的优点是随机访问速度快,因为可以通过数组下标直接访问元素;缺点是插入和删除元素的效率较低,因为需要移动大量元素。LinkedList 是基于双向链表实现的,插入和删除元素的效率高,只需要修改指针即可;但随机访问速度慢,需要从头或尾开始遍历链表。 面试官:非常棒,你的基础很扎实。

第二轮面试开始 面试官:接下来问一些关于多线程和 JUC 的问题。什么是线程池,为什么要使用线程池? 王铁牛:线程池就是一个管理线程的池子,它可以预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务。使用线程池可以减少线程创建和销毁的开销,提高系统的性能和稳定性,还可以对线程进行统一的管理和监控。 面试官:回答得不错。那在 JUC 中,CountDownLatch 和 CyclicBarrier 有什么区别? 王铁牛:这个……嗯……好像它们都是和线程同步有关的,具体区别我有点不太确定了。 面试官:没关系,接着问你,如何避免死锁的发生? 王铁牛:死锁就是多个线程互相等待对方释放资源,导致程序无法继续执行。要避免死锁,可以按照一定的顺序获取锁,避免一个线程同时获取多个锁,还可以设置锁的超时时间。 面试官:这个回答还可以,但对于刚才 CountDownLatch 和 CyclicBarrier 的问题,你需要下去再巩固一下。

第三轮面试开始 面试官:现在来谈谈框架相关的问题。Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有依赖注入和面向切面编程。依赖注入就是把对象的创建和依赖关系的管理交给 Spring 容器来完成,降低了代码的耦合度;面向切面编程可以在不修改原有代码的情况下,对程序进行增强,比如实现日志记录、事务管理等功能。 面试官:回答得挺好。那 Spring Boot 和 Spring 有什么关系,Spring Boot 的优势是什么? 王铁牛:Spring Boot 是基于 Spring 构建的,它简化了 Spring 应用的开发过程。Spring Boot 可以自动配置很多常用的组件,减少了大量的配置文件,还提供了嵌入式服务器,方便开发和部署。 面试官:不错。那 MyBatis 是如何实现 SQL 语句和 Java 代码的映射的? 王铁牛:这个……我记得好像是通过 XML 文件或者注解来定义 SQL 语句和 Java 对象之间的映射关系,但具体细节我不太清楚了。 面试官:对于 MyBatis 的这块知识你掌握得不够扎实。另外,像 Dubbo、RabbitMq、xxl - job、Redis 这些技术,本次面试没有详细考察,但你也需要全面掌握。

面试结束,面试官看着王铁牛说:“今天的面试就到这里,你回去等通知吧。整体来看,你对一些基础的 Java 知识掌握得还可以,但对于 JUC、MyBatis 等部分知识的理解还不够深入,后续可以进一步学习和巩固。我们会综合评估所有面试者的情况,有结果会及时通知你。”

问题答案详细解析

  1. Java 中多态的实现方式有哪些?
    • 方法重载(Overloading):在同一个类中,允许存在多个同名方法,但这些方法的参数列表(参数的类型、个数、顺序)必须不同。调用时,编译器会根据传入的参数来决定调用哪个方法。例如:
public class OverloadingExample {
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }
}
- 方法重写(Overriding):子类对父类中允许访问的方法进行重新编写。方法名、参数列表和返回值类型都要和父类的方法一致,但方法体可以不同。在运行时,根据对象的实际类型来决定调用哪个类的方法。例如:
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}
  1. HashMap 的底层数据结构是什么?
    • JDK 1.8 之前:采用数组加链表的结构。数组是 HashMap 的主体,链表是为了解决哈希冲突而存在的。当两个不同的键通过哈希函数计算得到相同的数组下标时,就会发生哈希冲突,这些冲突的键值对会以链表的形式存储在该数组下标对应的位置。
    • JDK 1.8 之后:采用数组加链表加红黑树的结构。当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树。红黑树是一种自平衡的二叉搜索树,它的查找、插入和删除操作的时间复杂度为 O(log n),相比链表的 O(n) 效率更高。当链表长度小于 6 时,红黑树会转化为链表。
  2. ArrayList 和 LinkedList 的区别是什么?
    • 数据结构:ArrayList 基于动态数组实现,它会在内存中分配一段连续的空间来存储元素;LinkedList 基于双向链表实现,每个节点包含数据和指向前一个节点和后一个节点的引用。
    • 随机访问效率:ArrayList 支持随机访问,通过数组下标可以直接访问元素,时间复杂度为 O(1);LinkedList 不支持随机访问,需要从头或尾开始遍历链表,时间复杂度为 O(n)。
    • 插入和删除效率:ArrayList 在插入和删除元素时,需要移动大量元素,时间复杂度为 O(n);LinkedList 在插入和删除元素时,只需要修改指针,时间复杂度为 O(1),但如果要插入或删除指定位置的元素,需要先遍历到该位置,时间复杂度为 O(n)。
  3. 什么是线程池,为什么要使用线程池?
    • 线程池:是一种线程管理机制,它预先创建一定数量的线程,并将这些线程存储在一个池子中。当有任务提交时,从线程池中获取线程来执行任务,任务执行完毕后,线程不会销毁,而是返回线程池等待下一个任务。
    • 使用线程池的原因
      • 减少线程创建和销毁的开销:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,提高系统的性能。
      • 提高系统的响应速度:当有任务提交时,线程池中已经有空闲线程可以立即执行任务,不需要等待线程的创建。
      • 统一管理和监控线程:可以对线程池中的线程进行统一的管理和监控,例如设置线程的数量、线程的优先级等。
  4. 在 JUC 中,CountDownLatch 和 CyclicBarrier 有什么区别?
    • CountDownLatch:是一个同步工具类,它允许一个或多个线程等待其他线程完成操作。它使用一个计数器来实现,计数器的初始值是线程的数量。每个线程完成任务后,计数器的值减 1。当计数器的值为 0 时,等待的线程可以继续执行。例如:
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " is working");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        System.out.println("All threads have finished working");
    }
}
- **CyclicBarrier**:也是一个同步工具类,它允许一组线程相互等待,直到所有线程都到达一个屏障点,然后所有线程可以继续执行。它有一个初始的计数器,当一个线程到达屏障点时,计数器的值减 1,当计数器的值为 0 时,所有线程会被释放,继续执行。与 CountDownLatch 不同的是,CyclicBarrier 可以重复使用。例如:
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("All threads have reached the barrier");
        });
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " is working");
                try {
                    Thread.sleep(1000);
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + " continues working");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
  1. 如何避免死锁的发生?
    • 破坏互斥条件:互斥条件是指一个资源在同一时间只能被一个线程访问。在大多数情况下,互斥条件是无法破坏的,因为有些资源本身就具有独占性。
    • 破坏占有并等待条件:可以让线程在获取所有需要的资源后再开始执行,或者在释放已占有的资源后再去获取其他资源。例如,使用一次性分配所有资源的策略。
    • 破坏不剥夺条件:当一个线程持有某些资源并请求其他资源时,如果请求失败,可以主动释放已持有的资源。
    • 破坏循环等待条件:可以对资源进行排序,让线程按照一定的顺序获取资源。例如,线程只能按照资源编号从小到大的顺序获取资源,这样就可以避免循环等待。
  2. Spring 的核心特性有哪些?
    • 依赖注入(Dependency Injection,DI):是一种设计模式,它将对象的创建和依赖关系的管理交给 Spring 容器来完成。通过依赖注入,对象之间的耦合度降低,代码的可维护性和可测试性提高。例如:
public class UserService {
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void saveUser() {
        userDao.save();
    }
}
- **面向切面编程(Aspect - Oriented Programming,AOP)**:可以在不修改原有代码的情况下,对程序进行增强。它将一些通用的功能(如日志记录、事务管理等)从业务逻辑中分离出来,形成独立的切面。在程序运行时,Spring 会将切面的逻辑织入到目标方法中。例如:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
    @After("execution(* com.example.service.*.*(..))")
    public void logAfterMethod() {
        System.out.println("Method has been executed");
    }
}
  1. Spring Boot 和 Spring 有什么关系,Spring Boot 的优势是什么?
    • 关系:Spring Boot 是基于 Spring 构建的,它是 Spring 的扩展和简化。Spring Boot 继承了 Spring 的核心特性,如依赖注入和面向切面编程。
    • 优势
      • 自动配置:Spring Boot 可以根据项目的依赖自动配置很多常用的组件,减少了大量的配置文件。例如,当项目中引入了 Spring Data JPA 的依赖时,Spring Boot 会自动配置数据源和 JPA 相关的组件。
      • 嵌入式服务器:Spring Boot 内置了嵌入式服务器(如 Tomcat、Jetty 等),可以直接将应用打包成可执行的 JAR 文件,方便开发和部署。
      • 起步依赖:Spring Boot 提供了一系列的起步依赖,这些依赖包含了一组相关的依赖,开发者只需要引入一个起步依赖,就可以快速搭建项目。例如,引入 spring - boot - starter - web 依赖,就可以快速搭建一个 Web 应用。
  2. MyBatis 是如何实现 SQL 语句和 Java 代码的映射的?
    • XML 映射文件:可以通过 XML 文件来定义 SQL 语句和 Java 对象之间的映射关系。在 XML 文件中,可以定义 SQL 语句、参数映射、结果映射等。例如:
<mapper namespace="com.example.dao.UserDao">
    <select id="getUserById" parameterType="int" resultType="com.example.entity.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>
- **注解方式**:也可以使用注解来定义 SQL 语句和 Java 对象之间的映射关系。例如:
import org.apache.ibatis.annotations.Select;

public interface UserDao {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(int id);
}

在使用时,MyBatis 会根据映射关系将 SQL 语句的执行结果映射到 Java 对象中。