《互联网大厂面试:从 Java 核心到分布式技术的硬核考验》

47 阅读10分钟

《互联网大厂面试:从 Java 核心到分布式技术的硬核考验》

在互联网大厂的一间安静面试室内,严肃的面试官正襟危坐,对面坐着紧张又满怀期待的求职者王铁牛。一场关于 Java 技术的面试拉开了帷幕。

第一轮面试 面试官:我们先从 Java 核心知识开始。你能说说 Java 中基本数据类型有哪些吗? 王铁牛:Java 的基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那你讲讲 String 类为什么是不可变的呢? 王铁牛:因为 String 类被 final 修饰,它的成员变量 value 数组也是 final 的,所以创建后就不能改变了。 面试官:很好。那在 Java 里,重载和重写的区别是什么? 王铁牛:重载是在一个类中,方法名相同但参数列表不同;重写是子类重写父类的方法,方法名、参数列表和返回值类型都一样,访问权限不能比父类小。 面试官:非常棒,看来你对 Java 核心知识掌握得很扎实。

第二轮面试 面试官:接下来聊聊 JUC 和多线程。你能说说什么是线程安全吗? 王铁牛:就是在多线程环境下,对共享资源的访问不会出现数据不一致等问题。 面试官:那你讲讲 Java 中实现多线程有哪些方式? 王铁牛:有继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池这几种方式。 面试官:不错。那你知道线程池有哪些核心参数吗? 王铁牛:有核心线程数、最大线程数、空闲线程存活时间、时间单位、任务队列、线程工厂和拒绝策略。 面试官:很好,对多线程和线程池有一定的了解。那你说说线程池的拒绝策略有哪些? 王铁牛:有 AbortPolicy、CallerRunsPolicy、DiscardPolicy 和 DiscardOldestPolicy。 面试官:回答得很全面。

第三轮面试 面试官:现在说说一些框架相关的。你讲讲 Spring 的 IOC 和 AOP 是什么? 王铁牛:IOC 是控制反转,把对象的创建和管理交给 Spring 容器;AOP 是面向切面编程,通过代理模式在不修改原有代码的情况下增强功能。 面试官:那 Spring Boot 有什么优点呢? 王铁牛:它简化了 Spring 开发,有自动配置、内嵌服务器、starter 依赖等优点。 面试官:MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:这个……#{} 是预编译处理,能防止 SQL 注入;{} 是直接替换,可能有 SQL 注入风险。 面试官:那 Dubbo 是做什么的,它的核心组件有哪些? 王铁牛:Dubbo 是分布式服务框架,核心组件有服务提供者、服务消费者、注册中心和监控中心。 面试官:最后说说 Redis,它有哪些数据类型? 王铁牛:有字符串、哈希、列表、集合、有序集合。

面试官总结:王铁牛,通过这三轮面试,我能看到你对一些基础的 Java 知识掌握得还可以,像 Java 核心知识、多线程和线程池,以及部分框架的基本概念都能答上来,表现出了你有一定的学习和知识储备。但在一些细节和深入的问题上,还需要进一步加强。比如对于某些技术在实际业务场景中的应用和原理,回答得还不够清晰和全面。后续你可以多研究一些开源项目或者实际业务代码,加深对这些技术的理解和应用能力。目前面试先到这里,你回家等通知吧。

问题答案详细解析

  1. Java 基本数据类型
    • Java 有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(1 位)。基本数据类型存储的是具体的值,在内存中占用固定大小的空间。
  2. String 类不可变的原因
    • String 类被 final 修饰,意味着它不能被继承。其内部的成员变量 value 数组也是 final 的,该数组存储了字符串的实际字符数据。一旦 String 对象被创建,value 数组的引用不能改变,而且数组中的元素也不能被修改。这样设计的好处是保证了字符串的安全性、线程安全,并且可以作为哈希表的键。
  3. 重载和重写的区别
    • 重载(Overloading):发生在同一个类中,方法名相同但参数列表不同(参数的类型、个数、顺序不同)。重载与返回值类型和访问修饰符无关。编译器根据调用方法时传入的参数来决定调用哪个重载方法。
    • 重写(Overriding):发生在子类和父类之间,子类重写父类的方法。要求方法名、参数列表和返回值类型都相同(Java 5 以后返回值类型可以是父类方法返回值类型的子类),访问权限不能比父类小,不能抛出比父类更多的异常。重写是实现多态的一种方式,通过父类引用指向子类对象,调用重写方法时会根据实际对象类型调用子类的方法。
  4. 线程安全
    • 在多线程环境下,当多个线程同时访问共享资源时,如果不采取任何同步措施,可能会导致数据不一致、脏读、幻读等问题。线程安全就是要保证在多线程访问共享资源时,不会出现这些问题。实现线程安全的方式有使用同步机制(如 synchronized 关键字、Lock 接口)、使用线程安全的类(如 Vector、ConcurrentHashMap)等。
  5. Java 实现多线程的方式
    • 继承 Thread 类:创建一个类继承 Thread 类,重写 run() 方法,在 run() 方法中定义线程要执行的任务。然后创建该类的对象,调用 start() 方法启动线程。
    • 实现 Runnable 接口:创建一个类实现 Runnable 接口,实现 run() 方法。然后创建该类的对象,将其作为参数传递给 Thread 类的构造函数,再调用 Thread 对象的 start() 方法启动线程。这种方式避免了单继承的限制。
    • 实现 Callable 接口:创建一个类实现 Callable 接口,实现 call() 方法,该方法有返回值。通过 FutureTask 类包装 Callable 对象,再将 FutureTask 对象传递给 Thread 类的构造函数,调用 start() 方法启动线程。可以通过 FutureTask 的 get() 方法获取线程执行的结果。
    • 使用线程池:线程池可以管理和复用线程,提高线程的使用效率。通过 Executors 工厂类可以创建不同类型的线程池,如 FixedThreadPool、CachedThreadPool 等。将实现了 Runnable 或 Callable 接口的任务提交给线程池执行。
  6. 线程池的核心参数
    • 核心线程数(corePoolSize):线程池中的基本线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
    • 最大线程数(maximumPoolSize):线程池允许创建的最大线程数量。当任务队列已满,且线程数小于最大线程数时,线程池会创建新的线程来执行任务。
    • 空闲线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数时,多余的空闲线程在经过 keepAliveTime 时间后会被销毁。
    • 时间单位(unit):keepAliveTime 的时间单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
    • 任务队列(workQueue):用于存储等待执行的任务的队列。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
    • 线程工厂(threadFactory):用于创建线程的工厂类,可以自定义线程的名称、优先级等属性。
    • 拒绝策略(handler):当任务队列已满,且线程数达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程来执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后尝试提交新任务)。
  7. Spring 的 IOC 和 AOP
    • IOC(Inversion of Control,控制反转):传统的对象创建和管理是由程序代码自己负责,而在 Spring 中,将对象的创建和管理交给了 Spring 容器。容器会根据配置信息(如 XML 配置文件、注解)来创建和管理对象,并将对象之间的依赖关系注入到相应的对象中。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。
    • AOP(Aspect Oriented Programming,面向切面编程):它是对面向对象编程的一种补充。在程序中,有些功能(如日志记录、事务管理、权限验证等)会分散在多个业务逻辑中,使用 AOP 可以将这些横切关注点(Cross - cutting Concerns)提取出来,形成独立的切面(Aspect)。通过代理模式(JDK 动态代理或 CGLIB 代理),在不修改原有业务代码的情况下,将切面的功能织入到目标方法的前后或周围,实现功能的增强。
  8. Spring Boot 的优点
    • 简化开发:Spring Boot 提供了自动配置功能,根据项目中引入的依赖,自动配置 Spring 应用的各种组件,减少了大量的 XML 配置文件和样板代码。
    • 内嵌服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,不需要单独部署服务器,直接运行应用程序即可。
    • starter 依赖:Spring Boot 提供了一系列的 starter 依赖,通过引入相应的 starter 依赖,就可以快速集成各种功能,如 Spring Web、Spring Data JPA 等。
    • 监控和管理:Spring Boot Actuator 提供了对应用程序的监控和管理功能,如查看应用的健康状态、查看配置信息等。
  9. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符?,然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入攻击,因为参数会经过预编译处理,不会直接拼接到 SQL 语句中。
    • :是直接替换,MyBatis在处理{}:是直接替换,MyBatis 在处理 {} 时,会将 直接替换为参数的值。这种方式可能会导致SQL注入攻击,因为参数是直接拼接到SQL语句中的。一般在需要动态传入表名、列名等场景下使用{} 直接替换为参数的值。这种方式可能会导致 SQL 注入攻击,因为参数是直接拼接到 SQL 语句中的。一般在需要动态传入表名、列名等场景下使用 {},但要注意对传入的参数进行严格的验证和过滤。
  10. Dubbo 的作用和核心组件
    • 作用:Dubbo 是一个高性能的分布式服务框架,用于解决分布式系统中服务之间的远程调用和服务治理问题。它可以将不同的服务提供者和服务消费者连接起来,实现服务的注册、发现、调用和监控。
    • 核心组件:
      • 服务提供者(Provider):提供具体的服务实现,将服务注册到注册中心。
      • 服务消费者(Consumer):从注册中心获取服务提供者的地址信息,调用服务提供者提供的服务。
      • 注册中心(Registry):负责服务的注册和发现,服务提供者将自己的服务信息注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息。常见的注册中心有 ZooKeeper、Nacos 等。
      • 监控中心(Monitor):收集服务的调用信息,如调用次数、调用时间、成功率等,用于监控服务的运行状态和性能。
  11. Redis 的数据类型
    • 字符串(String):是 Redis 最基本的数据类型,可以存储字符串、整数或浮点数。常见的操作有 set、get、incr、decr 等。
    • 哈希(Hash):是一个键值对的集合,适合存储对象。每个哈希可以存储多个字段和值,常见的操作有 hset、hget、hgetall 等。
    • 列表(List):是一个双向链表,可以在列表的两端进行插入和删除操作。常见的操作有 lpush、rpush、lpop、rpop 等。
    • 集合(Set):是一个无序且唯一的元素集合,支持集合的交集、并集、差集等操作。常见的操作有 sadd、srem、sinter、sunion 等。
    • 有序集合(Sorted Set):是一个有序的元素集合,每个元素都有一个分数(score),根据分数进行排序。常见的操作有 zadd、zrange、zrank 等。