《互联网大厂面试:Java核心、并发、框架与中间件大考验》

42 阅读12分钟

互联网大厂面试:Java核心、并发、框架与中间件大考验

第一轮面试 面试官:你好,先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:Java 的基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那自动装箱和拆箱是怎么回事? 王铁牛:自动装箱就是把基本数据类型转换为对应的包装类,拆箱就是把包装类转换为基本数据类型。比如 Integer i = 10 就是自动装箱,int j = i 就是拆箱。 面试官:很好。那 String 类是不可变的,你能说下原因和好处吗? 王铁牛:因为 String 类内部是用 final 修饰的字符数组存储字符串的,所以不可变。好处是可以保证线程安全,还能提高缓存性能。

第二轮面试 面试官:现在考考你 JUC 和多线程的知识。什么是线程安全? 王铁牛:线程安全就是多个线程访问同一个对象时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步或协同,这个对象都能表现出正确的行为。 面试官:不错。那说说 synchronized 和 Lock 的区别。 王铁牛:synchronized 是 Java 内置的关键字,Lock 是一个接口。synchronized 会自动释放锁,Lock 需要手动释放。 面试官:可以。那线程池有哪些核心参数? 王铁牛:线程池的核心参数有 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程存活时间)、TimeUnit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)、handler(拒绝策略)。 面试官:回答得很全面。那说说线程池的工作流程。 王铁牛:当提交一个新任务时,线程池先判断核心线程是否都在工作,如果有空闲的核心线程,就用核心线程执行任务;如果核心线程都在工作,就把任务放到任务队列里;如果任务队列满了,就判断线程数是否达到最大线程数,如果没达到,就创建新线程执行任务;如果达到最大线程数,就执行拒绝策略。

第三轮面试 面试官:接下来问一些框架和中间件的问题。Spring 的 IOC 和 AOP 是什么? 王铁牛:IOC 是控制反转,就是把对象的创建和依赖关系的管理交给 Spring 容器。AOP 是面向切面编程,就是在不修改原有代码的基础上,对程序进行增强。 面试官:还可以。那 Spring Boot 有什么优点? 王铁牛:Spring Boot 可以快速搭建项目,简化配置,有很多 Starter 可以使用,还能内嵌 Tomcat 等服务器。 面试官:行。那 MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译处理,会把参数变成占位符,可以防止 SQL 注入;{} 是直接替换,可能会有 SQL 注入风险。 面试官:好。那 Dubbo 是什么,有什么作用? 王铁牛:Dubbo 是一个高性能的 Java RPC 框架,可以实现服务的远程调用。 面试官:不错。那 Redis 有哪些数据类型? 王铁牛:Redis 有字符串、哈希、列表、集合、有序集合这些数据类型。

面试官总结:整体来看,你对一些基础的 Java 知识和常见的框架、中间件有一定的了解。在回答 Java 核心知识、线程池的参数和工作流程、Spring 的 IOC 和 AOP 等问题时,表现得比较不错,能够准确清晰地说出关键要点,说明你对这些内容有扎实的学习。不过,对于一些相对复杂的原理和深入的应用场景,回答得还不够完善和深入。比如在阐述某些技术在实际业务场景中的具体应用案例时,你没有给出很详细的说明。在后续的学习中,你可以多结合实际项目去理解这些技术的使用,加深对它们的掌握。目前面试就到这里,你回家等通知吧。

问题答案详解

第一轮问题答案

  1. Java 中基本数据类型有哪些?
    • Java 的基本数据类型分为四类八种:
      • 整数类型:byte(1 字节,范围 -128 到 127)、short(2 字节)、int(4 字节)、long(8 字节)。
      • 浮点类型:float(4 字节)、double(8 字节)。
      • 字符类型:char(2 字节)。
      • 布尔类型:boolean(理论上 1 位,但实际在 Java 中一般作为 1 字节处理)。
  2. 自动装箱和拆箱是怎么回事?
    • 自动装箱是 Java 编译器在基本数据类型和对应的包装类之间进行的自动转换。例如,当把一个基本数据类型赋值给对应的包装类对象时,编译器会自动调用包装类的 valueOf() 方法进行装箱操作。如 Integer i = 10; 实际上是 Integer i = Integer.valueOf(10);
    • 自动拆箱是相反的过程,当把一个包装类对象赋值给基本数据类型时,编译器会自动调用包装类的 xxxValue() 方法进行拆箱操作。如 int j = i; 实际上是 int j = i.intValue();
  3. String 类是不可变的,你能说下原因和好处吗?
    • 原因:String 类内部使用 private final char value[]; 来存储字符串,final 关键字修饰的数组意味着数组的引用不能被改变,而且 String 类没有提供修改字符数组内容的方法,所以 String 对象一旦创建,其内容不能被修改。
    • 好处:
      • 线程安全:由于 String 不可变,多个线程可以同时访问同一个 String 对象,不会出现线程安全问题。
      • 缓存性能:String 类有一个字符串常量池,当创建一个 String 对象时,如果常量池中已经存在相同内容的字符串,就会直接返回常量池中的引用,避免重复创建对象,节省内存。
      • 作为哈希键:因为不可变,所以其哈希码可以被缓存,提高哈希表的性能。

第二轮问题答案

  1. 什么是线程安全?
    • 线程安全是指在多线程环境下,对共享资源的访问和操作不会导致数据的不一致或产生其他错误。当多个线程同时访问一个对象时,如果这个对象的状态不会因为线程的交替执行而受到破坏,并且在没有额外同步措施的情况下,对象的行为仍然符合预期,那么这个对象就是线程安全的。例如,String 类就是线程安全的,因为它是不可变的,多个线程可以同时读取同一个 String 对象而不会产生冲突。
  2. 说说 synchronized 和 Lock 的区别。
    • 语法层面:
      • synchronized 是 Java 内置的关键字,使用起来比较简单,不需要手动释放锁,当同步代码块或同步方法执行完毕,锁会自动释放。例如:
public synchronized void method() {
    // 同步代码
}
    - `Lock` 是一个接口,常用的实现类有 `ReentrantLock`。使用 `Lock` 需要手动加锁和解锁,通常需要在 `finally` 块中释放锁,以确保锁一定会被释放。例如:
Lock lock = new ReentrantLock();
try {
    lock.lock();
    // 同步代码
} finally {
    lock.unlock();
}
- 锁的获取和释放:
    - `synchronized` 是隐式锁,由 JVM 自动管理锁的获取和释放。
    - `Lock` 是显式锁,需要程序员手动控制锁的获取和释放,提供了更多的灵活性,比如可以尝试获取锁(`tryLock()` 方法),在一定时间内获取锁等。
- 锁的特性:
    - `synchronized` 是可重入、非公平锁。
    - `Lock` 可以通过构造函数指定是公平锁还是非公平锁,默认是非公平锁。

3. 线程池有哪些核心参数? - corePoolSize:核心线程数,线程池在初始化时会创建的线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。 - maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。当任务队列满了,并且线程数小于最大线程数时,线程池会创建新的线程来执行任务。 - keepAliveTime:线程存活时间,当线程池中的线程数量大于核心线程数时,多余的空闲线程在等待新任务的最长时间,超过这个时间,线程会被销毁。 - TimeUnit:时间单位,用于指定 keepAliveTime 的时间单位,如 TimeUnit.SECONDS 表示秒。 - workQueue:任务队列,用于存储提交的任务。常用的任务队列有 ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)、SynchronousQueue(直接提交队列)等。 - threadFactory:线程工厂,用于创建线程。可以自定义线程工厂,为线程设置名称、优先级等属性。 - handler:拒绝策略,当任务队列满了,并且线程数达到最大线程数时,新提交的任务会触发拒绝策略。常用的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。 4. 说说线程池的工作流程。 - 当提交一个新任务时,线程池会按照以下步骤处理: - 步骤 1:判断核心线程是否都在工作。如果有空闲的核心线程,就从核心线程中选择一个来执行任务。 - 步骤 2:如果核心线程都在工作,就将任务放入任务队列中。 - 步骤 3:如果任务队列已满,判断线程数是否达到最大线程数。如果没达到,就创建新的线程来执行任务。 - 步骤 4:如果线程数已经达到最大线程数,就执行拒绝策略。

第三轮问题答案

  1. Spring 的 IOC 和 AOP 是什么?
    • IOC(控制反转):是一种设计原则,在 Spring 中,IOC 指的是把对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。传统的开发中,对象的创建和依赖关系是由程序员在代码中手动控制的,而在 Spring 中,通过配置文件或注解的方式,将对象的创建和依赖关系交给 Spring 容器来管理。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。例如,通过 @Autowired 注解可以实现对象的自动注入。
    • AOP(面向切面编程):是一种编程范式,它允许开发者在不修改原有代码的基础上,对程序进行增强。在 AOP 中,把横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,形成独立的切面。通过在特定的连接点(如方法调用前后)织入切面代码,可以实现对程序的增强。Spring AOP 主要基于动态代理实现,有 JDK 动态代理和 CGLIB 动态代理两种方式。
  2. Spring Boot 有什么优点?
    • 快速搭建项目:Spring Boot 提供了很多 Starter 依赖,只需要在 pom.xmlbuild.gradle 中添加相应的 Starter 依赖,就可以快速集成各种功能,减少了繁琐的配置。
    • 简化配置:Spring Boot 采用约定大于配置的原则,默认提供了很多合理的配置,开发者只需要进行少量的配置就可以满足大部分需求。同时,还支持使用 application.propertiesapplication.yml 文件进行自定义配置。
    • 内嵌服务器:Spring Boot 可以内嵌 Tomcat、Jetty 等服务器,不需要单独部署服务器,直接运行项目的主类就可以启动应用程序,方便开发和测试。
    • 监控和管理:Spring Boot Actuator 提供了对应用程序的监控和管理功能,如查看应用程序的健康状态、内存使用情况、线程信息等。
  3. MyBatis 中 #{} 和 ${} 的区别是什么?
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入攻击,因为参数会被自动进行类型转换和转义。例如:
<select id="selectUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是直接替换,MyBatis 在处理 `${}` 时,会将 `${}` 直接替换为参数的值,不会进行预编译。这样可能会导致 SQL 注入攻击,因为参数的值会直接拼接到 SQL 语句中。例如:
<select id="selectUserByColumnName" parameterType="String" resultType="User">
    SELECT * FROM users WHERE ${columnName} = 'value'
</select>
一般情况下,建议使用 `#{} `来防止 SQL 注入,只有在需要动态传入表名、列名等特殊情况下才使用 `${}`

4. Dubbo 是什么,有什么作用? - Dubbo 是阿里巴巴开源的一个高性能的 Java RPC(远程过程调用)框架,用于实现不同服务之间的远程通信。它提供了服务注册与发现、远程调用、集群容错、负载均衡等功能,帮助开发者构建分布式系统。 - 作用: - 服务治理:Dubbo 提供了服务注册中心(如 ZooKeeper、Nacos 等),可以实现服务的自动注册和发现,方便服务的管理和调用。 - 远程调用:通过 Dubbo 可以像调用本地方法一样调用远程服务,隐藏了网络通信的细节,提高了开发效率。 - 集群容错:Dubbo 提供了多种集群容错策略,如失败重试、快速失败等,可以提高系统的可靠性和稳定性。 - 负载均衡:Dubbo 支持多种负载均衡算法,如随机、轮询、最少活跃调用数等,可以将请求均匀地分发到多个服务提供者上,提高系统的性能。 5. Redis 有哪些数据类型? - 字符串(String):是最基本的数据类型,可以存储字符串、整数、浮点数等。常见的操作有 SETGETINCR 等。例如:

SET key value
GET key
- **哈希(Hash)**:是一个键值对的集合,适合存储对象。每个哈希可以存储多个字段和值,常见的操作有 `HSET``HGET``HGETALL` 等。例如:
HSET user name "John"
HSET user age 30
HGETALL user
- **列表(List)**:是一个有序的字符串列表,可以从列表的两端进行插入和删除操作。常见的操作有 `LPUSH``RPUSH``LPOP``RPOP` 等。例如:
LPUSH list value1
RPUSH list value2
LPOP list
- **集合(Set)**:是一个无序且唯一的字符串集合,支持交集、并集、差集等操作。常见的操作有 `SADD``SMEMBERS``SINTER` 等。例如:
SADD set value1
SADD set value2
SMEMBERS set
- **有序集合(Sorted Set)**:是一个有序的字符串集合,每个成员都有一个分数,通过分数来进行排序。常见的操作有 `ZADD``ZRANGE``ZREVRANGE` 等。例如:
ZADD zset 10 value1
ZADD zset 20 value2
ZRANGE zset 0 -1