《互联网大厂面试:从 Java 核心到分布式技术的全方位考察》

54 阅读12分钟

《互联网大厂面试:从 Java 核心到分布式技术的全方位考察》

在互联网大厂宽敞明亮的面试间里,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。面试即将开始,一场关于 Java 技术深度与广度的考验拉开了帷幕。

第一轮提问 面试官:“首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些?” 王铁牛:“Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。” 面试官:“回答正确,很不错。那 Java 中 String 类为什么是不可变的?” 王铁牛:“因为 String 类是用 final 修饰的,它的底层是一个 final 修饰的 char 数组,一旦创建就不能再改变。” 面试官:“非常好。再问一个,Java 中重写和重载的区别是什么?” 王铁牛:“重写是子类对父类中已有方法的实现进行重新编写,方法名、参数列表和返回值类型都要相同;重载是在同一个类中,方法名相同但参数列表不同。” 面试官:“回答得很清晰,这一轮表现不错。”

第二轮提问 面试官:“接下来考察一下 JUC 和多线程相关知识。什么是线程安全?” 王铁牛:“线程安全就是在多线程环境下,程序的运行结果和单线程环境下的运行结果一样,不会出现数据不一致等问题。” 面试官:“回答正确。那 JUC 中 CountDownLatch 是如何使用的,有什么应用场景?” 王铁牛:“嗯……那个,好像是可以让一个线程等待其他线程完成任务后再执行,应用场景……就是等待一些初始化操作完成吧。”(回答不够清晰) 面试官:“表述有点模糊。再问,线程池有哪些创建方式,各有什么特点?” 王铁牛:“可以通过 Executors 工具类创建,像 FixedThreadPool 是固定大小的线程池,还有 CachedThreadPool 是可缓存的线程池……其他的我不太记得全了。”(回答不完整) 面试官:“这一轮对简单问题回答还行,但复杂点的问题回答不够准确和完整。”

第三轮提问 面试官:“最后来考考框架和中间件知识。Spring 中 IOC 和 AOP 是什么,有什么作用?” 王铁牛:“IOC 是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器,降低了代码的耦合度;AOP 是面向切面编程,可以在不修改原有代码的情况下,对程序进行增强,比如实现日志记录、事务管理等。” 面试官:“回答得不错。那 Redis 有哪些数据结构,在实际业务中如何应用?” 王铁牛:“有字符串、哈希、列表、集合、有序集合。应用嘛……字符串可以做缓存,哈希可以存对象,其他的……我不太清楚具体业务场景了。”(回答不全面) 面试官:“有一定了解,但不够深入。最后问一个,Dubbo 是什么,它的工作原理是怎样的?” 王铁牛:“Dubbo 是一个分布式服务框架,原理……好像是服务提供者注册服务,消费者调用服务,中间有个注册中心,但具体细节我不太清楚了。”(回答不清晰) 面试官:“这一轮对于一些基础概念能回答,但深入的业务应用和原理方面回答得不够理想。你先回家等通知吧,后续如果有结果会及时联系你。”

问题答案详解

第一轮

  1. Java 中基本数据类型有哪些? Java 有 8 种基本数据类型,可分为 4 类:
    • 整数类型
      • byte:8 位,有符号,范围是 -128 到 127。
      • short:16 位,有符号,范围是 -32768 到 32767。
      • int:32 位,有符号,范围是 -2147483648 到 2147483647。
      • long:64 位,有符号,范围更大,使用时需要在数字后面加 Ll
    • 浮点类型
      • float:32 位,单精度浮点数,使用时需要在数字后面加 Ff
      • double:64 位,双精度浮点数,是浮点数的默认类型。
    • 字符类型
      • char:16 位,用于表示单个字符,使用单引号括起来。
    • 布尔类型
      • boolean:只有两个值 truefalse,用于逻辑判断。
  2. Java 中 String 类为什么是不可变的? String 类是不可变的主要原因有以下几点:
    • String 类被 final 修饰,意味着它不能被继承。
    • 其底层是一个 final 修饰的 char 数组,final 修饰数组表示数组的引用不能改变,即不能指向新的数组。并且 String 类没有提供修改数组元素的方法,所以一旦创建就不能再改变。
    • 不可变的特性使得 String 对象可以被安全地共享,例如在多线程环境下不需要额外的同步机制,同时也提高了性能,因为可以缓存 String 对象。
  3. Java 中重写和重载的区别是什么?
    • 重写(Override)
      • 发生在子类和父类之间,子类对父类中已有方法的实现进行重新编写。
      • 方法名、参数列表和返回值类型都要相同(子类方法的返回值类型可以是父类方法返回值类型的子类,这是协变返回类型)。
      • 访问修饰符不能比父类中被重写的方法更严格。
      • 主要用于实现多态,让子类可以根据自身的需求提供不同的实现。
    • 重载(Overload)
      • 发生在同一个类中,方法名相同但参数列表不同。
      • 参数列表的不同可以是参数的类型、个数或顺序不同。
      • 返回值类型和访问修饰符可以不同。
      • 主要用于提供多种不同的调用方式,方便调用者根据不同的情况选择合适的方法。

第二轮

  1. 什么是线程安全? 线程安全是指在多线程环境下,程序的运行结果和单线程环境下的运行结果一样,不会出现数据不一致、数据丢失等问题。当多个线程同时访问共享资源时,如果对共享资源的访问进行了正确的同步控制,保证在同一时刻只有一个线程可以访问或修改共享资源,那么这个程序就是线程安全的。例如,StringBuffer 是线程安全的,因为它的方法都使用了 synchronized 关键字进行同步,而 StringBuilder 是非线程安全的。
  2. JUC 中 CountDownLatch 是如何使用的,有什么应用场景? CountDownLatchjava.util.concurrent 包下的一个同步工具类,它允许一个或多个线程等待其他线程完成操作。使用步骤如下:
    • 创建 CountDownLatch 对象,指定初始计数,例如 CountDownLatch latch = new CountDownLatch(3); 表示需要等待 3 个线程完成操作。
    • 在需要等待的线程中调用 latch.await() 方法,该线程会被阻塞,直到计数为 0。
    • 在其他线程完成操作后,调用 latch.countDown() 方法将计数减 1。 应用场景:
    • 主线程等待多个子线程完成初始化操作后再继续执行,例如在多线程下载任务中,主线程等待所有子线程下载完成后再进行合并操作。
    • 多个子线程等待某个条件满足后同时开始执行,例如多个运动员等待裁判发令枪响后同时起跑。
  3. 线程池有哪些创建方式,各有什么特点? 线程池可以通过 Executors 工具类创建,也可以通过 ThreadPoolExecutor 手动创建,常见的创建方式及特点如下:
    • FixedThreadPool:通过 Executors.newFixedThreadPool(int nThreads) 创建,创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程则立即执行,否则将任务放入队列中等待。适用于需要控制并发线程数量的场景。
    • CachedThreadPool:通过 Executors.newCachedThreadPool() 创建,创建一个可缓存的线程池,线程数量不固定,当有新任务提交时,如果线程池中有空闲线程则立即执行,否则创建新线程执行任务。如果线程空闲时间超过 60 秒则会被回收。适用于执行大量短期异步任务的场景。
    • SingleThreadExecutor:通过 Executors.newSingleThreadExecutor() 创建,创建一个单线程的线程池,只有一个线程执行任务,保证任务按顺序执行。适用于需要保证任务顺序执行的场景。
    • ScheduledThreadPool:通过 Executors.newScheduledThreadPool(int corePoolSize) 创建,创建一个可定时执行任务的线程池,可以指定核心线程数量。适用于需要定时执行任务或周期性执行任务的场景。
    • 手动创建 ThreadPoolExecutor:可以根据具体需求设置线程池的核心线程数、最大线程数、线程空闲时间、任务队列等参数,更加灵活。

第三轮

  1. Spring 中 IOC 和 AOP 是什么,有什么作用?
    • IOC(控制反转)
      • 控制反转是一种设计思想,将对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象本身来创建和管理依赖对象。在传统的编程中,对象之间的依赖关系是由对象自己创建和维护的,而在 IOC 模式下,对象只需要声明自己需要的依赖,由 Spring 容器来负责创建和注入这些依赖。
      • 作用:降低了代码的耦合度,提高了代码的可维护性和可测试性。例如,一个类需要依赖另一个类的实例,通过 IOC 可以在配置文件或使用注解的方式将依赖注入进来,而不需要在类内部手动创建依赖对象。
    • AOP(面向切面编程)
      • 面向切面编程是一种编程范式,它允许在不修改原有代码的情况下,对程序进行增强。AOP 将那些与业务逻辑无关,但又被多个业务模块所共同调用的功能(如日志记录、事务管理、权限验证等)提取出来,形成一个独立的模块,称为切面。然后在需要的地方将切面织入到目标对象中。
      • 作用:提高了代码的复用性和可维护性,减少了代码的重复。例如,在多个业务方法中都需要记录日志,使用 AOP 可以将日志记录的代码集中到一个切面中,然后在需要记录日志的方法上进行织入。
  2. Redis 有哪些数据结构,在实际业务中如何应用?
    • 字符串(String)
      • 是最基本的数据结构,一个键对应一个值。
      • 应用场景:缓存数据,例如缓存用户信息、商品信息等;计数器,如统计网站的访问量、文章的阅读量等。
    • 哈希(Hash)
      • 是一个键值对的集合,适合存储对象。
      • 应用场景:存储对象,如用户信息、商品信息等,每个对象的属性作为哈希的字段,属性值作为哈希的值。
    • 列表(List)
      • 是一个有序的字符串列表,可以在列表的两端进行插入和删除操作。
      • 应用场景:消息队列,例如实现生产者 - 消费者模式;最新消息列表,如展示最新的文章列表、评论列表等。
    • 集合(Set)
      • 是一个无序且唯一的字符串集合。
      • 应用场景:去重,例如统计网站的独立访客数;交集、并集、差集运算,如找出两个用户的共同好友。
    • 有序集合(Sorted Set)
      • 是一个有序的字符串集合,每个成员都有一个分数,根据分数进行排序。
      • 应用场景:排行榜,如游戏的积分排行榜、文章的点赞排行榜等。
  3. Dubbo 是什么,它的工作原理是怎样的? Dubbo 是阿里巴巴开源的一个高性能、轻量级的分布式服务框架,用于解决分布式系统中服务之间的远程调用和服务治理问题。其工作原理如下:
    • 服务提供者(Provider):将自己提供的服务注册到注册中心,同时监听注册中心的变化。当有服务消费者调用服务时,服务提供者接收请求并处理,然后将结果返回给服务消费者。
    • 服务消费者(Consumer):从注册中心获取服务提供者的地址列表,根据负载均衡算法选择一个服务提供者进行调用。同时,服务消费者也会监听注册中心的变化,当服务提供者的地址发生变化时,会更新本地的服务提供者地址列表。
    • 注册中心(Registry):负责服务的注册和发现,服务提供者将自己的服务信息注册到注册中心,服务消费者从注册中心获取服务提供者的信息。常见的注册中心有 ZooKeeper、Redis 等。
    • 监控中心(Monitor):负责收集服务的调用信息,如调用次数、调用时间、成功率等,以便对服务的性能进行监控和分析。
    • 调用过程:服务消费者通过代理对象调用服务,代理对象将请求封装成网络消息发送给服务提供者,服务提供者接收到请求后进行处理,然后将结果返回给服务消费者。整个过程通过网络通信完成,Dubbo 支持多种协议,如 Dubbo 协议、HTTP 协议等。