《互联网大厂 Java 面试:从基础到进阶的核心知识点大考察》

52 阅读10分钟

第一轮面试 面试官:先从基础的Java核心知识问起。Java中多态是如何实现的? 王铁牛:多态就是子类重写父类的方法,然后通过父类引用指向子类对象来实现。 面试官:回答得不错。那ArrayList和HashMap在存储数据结构上有什么区别? 王铁牛:ArrayList是基于数组的,按顺序存储数据。HashMap是基于哈希表,通过键值对存储数据。 面试官:很好。接着问,Spring框架中IOC的作用是什么? 王铁牛:IOC就是控制反转,把对象的创建和管理交给Spring容器,这样代码耦合度就降低了。 面试官:回答得很准确。

第二轮面试 面试官:进入第二轮,聊聊JUC和多线程。线程池有哪些核心参数,分别有什么作用? 王铁牛:嗯……有核心线程数,好像是一直保留的线程数量。还有最大线程数,就是最多能创建的线程数。其他的……我有点记不太清了。 面试官:好,那说说多线程中如何保证线程安全? 王铁牛:可以用synchronized关键字,它能锁住代码块或者方法,保证同一时间只有一个线程能访问。还有……还有就是用Lock接口。 面试官:那在高并发场景下,使用HashMap会有什么问题? 王铁牛:好像是会出现数据丢失,因为它不是线程安全的。 面试官:回答得还算可以。

第三轮面试 面试官:最后一轮,问些更深入的。Dubbo在分布式系统中有哪些关键特性? 王铁牛:嗯……它好像能做服务治理,还有负载均衡,其他的不太清楚了。 面试官:那RabbitMQ在消息队列场景下,如何保证消息不丢失? 王铁牛:嗯……可以设置持久化,还有……还有确认机制,但是具体怎么弄我不太确定。 面试官:xxl - job在分布式任务调度中有什么优势? 王铁牛:它好像配置简单,然后能可视化管理任务,其他的就不知道了。 面试官:好,今天的面试就到这里。你对自己今天的表现应该也有一定认知,整体基础部分回答得还不错,但在一些深入的技术点上,理解还不够透彻。回去等通知吧,我们会综合评估所有候选人后,再做决定。

问题答案

  1. Java中多态是如何实现的:多态通过继承、重写和向上转型来实现。继承是多态的基础,子类继承父类获得其属性和方法。重写是子类对父类中可重写方法进行重新实现。向上转型是指用父类引用指向子类对象。例如,父类Animal有一个方法eat(),子类Dog继承Animal并重写eat()方法,当创建Dog对象并赋值给Animal引用时,调用eat()方法就会执行Dog类中重写的方法,这就体现了多态。
  2. ArrayList和HashMap在存储数据结构上有什么区别:ArrayList基于动态数组实现,它按顺序存储元素,元素在内存中连续存放,通过索引访问元素效率高,适合顺序访问和插入操作(尾部插入效率高,中间插入需要移动元素)。HashMap基于哈希表,通过哈希函数将键映射到一个桶(bucket)中,以键值对形式存储数据。它通过键获取值效率高,适合快速查找,但插入和删除操作可能涉及到哈希冲突处理,性能相对复杂一些。
  3. Spring框架中IOC的作用是什么:IOC(Inversion of Control)即控制反转,它将对象的创建和管理从应用程序代码转移到Spring容器中。传统方式下,对象的创建和依赖关系管理由应用程序自己负责,这导致代码耦合度高,维护和测试困难。使用IOC后,Spring容器负责创建对象、管理对象生命周期以及注入对象依赖。例如,一个Service类依赖另一个Dao类,在IOC模式下,Spring容器会创建这两个对象,并将Dao对象注入到Service对象中,使得Service类无需自己创建Dao对象,降低了耦合度,提高了代码的可维护性和可测试性。
  4. 线程池有哪些核心参数,分别有什么作用:线程池核心参数有:
    • 核心线程数(corePoolSize):线程池中会一直存活的线程数量,即使这些线程处于空闲状态,也不会被销毁。当有新任务提交时,如果线程池中的线程数小于核心线程数,会优先创建新线程来处理任务。
    • 最大线程数(maximumPoolSize):线程池中允许存在的最大线程数量。当任务队列已满且线程池中的线程数小于最大线程数时,会创建新线程来处理任务。
    • 队列容量(workQueue):用于存放等待处理任务的队列。当线程池中的线程数达到核心线程数后,新提交的任务会被放入队列中等待处理。常见的队列类型有ArrayBlockingQueue(有界数组队列)、LinkedBlockingQueue(无界链表队列)等。
    • 线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数时,多余的空闲线程在存活时间内没有新任务可处理,就会被销毁。
    • 拒绝策略(RejectedExecutionHandler):当任务队列已满且线程池中的线程数达到最大线程数时,新提交的任务会被拒绝,此时会执行拒绝策略。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(由提交任务的线程处理该任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后尝试提交新任务)。
  5. 多线程中如何保证线程安全
    • synchronized关键字:它可以修饰方法或代码块。修饰方法时,该方法成为同步方法,同一时间只有一个线程能进入该方法。修饰代码块时,需要指定一个锁对象,只有获取到该锁对象的线程才能进入代码块。例如,synchronized(this){// 同步代码块},它通过监视器锁(monitor)来实现线程同步,保证同一时间只有一个线程能访问被保护的代码。
    • Lock接口:提供了比synchronized更灵活的锁控制。例如ReentrantLock,它是可重入的互斥锁。使用时需要手动获取锁(lock()方法)和释放锁(unlock()方法),通常放在try - finally块中,以确保锁一定会被释放。它还支持公平锁和非公平锁模式,以及条件变量(Condition)等高级功能。
    • 原子类:如AtomicIntegerAtomicLong等,它们利用硬件级别的原子操作来保证数据的原子性,避免多线程环境下的数据竞争。例如AtomicIntegerincrementAndGet()方法能原子地增加其值,无需额外的同步操作。
  6. 在高并发场景下,使用HashMap会有什么问题:HashMap不是线程安全的,在高并发场景下可能会出现以下问题:
    • 数据丢失:当多个线程同时进行put操作时,如果发生哈希冲突,可能会导致数据覆盖,造成数据丢失。
    • 死循环:在JDK 1.7及之前版本,当HashMap进行扩容时,多个线程同时操作可能会导致链表形成环形结构,从而在获取元素时陷入死循环。在JDK 1.8中,虽然对HashMap的实现进行了优化,采用了数组 + 链表 + 红黑树的结构,但在高并发下仍可能出现数据不一致等问题。所以在高并发场景下,应使用线程安全的ConcurrentHashMap。
  7. Dubbo在分布式系统中有哪些关键特性
    • 服务治理:Dubbo提供了丰富的服务治理功能,包括服务注册与发现、服务监控、服务路由、服务降级、服务容错等。通过服务注册中心(如Zookeeper),服务提供者将自己的服务注册上去,服务消费者从注册中心获取服务列表,实现服务的动态发现和调用。
    • 负载均衡:Dubbo支持多种负载均衡策略,如随机(Random)、轮询(RoundRobin)、最少活跃调用数(LeastActive)、一致性哈希(ConsistentHash)等。负载均衡策略可以根据不同的业务场景选择,以提高系统的整体性能和可用性。
    • 高性能通信:Dubbo采用了多种高性能的通信协议,如Dubbo协议、RMI协议、HTTP协议等,支持多种序列化方式,如Hessian2、JSON等,能够满足不同场景下的通信需求,提高系统的通信效率。
    • 可扩展性:Dubbo的设计具有良好的可扩展性,用户可以通过SPI(Service Provider Interface)机制对Dubbo的各个功能模块进行扩展,如自定义负载均衡策略、自定义序列化方式等。
  8. RabbitMQ在消息队列场景下,如何保证消息不丢失
    • 生产者端
      • 开启确认机制(publisher confirm):生产者将消息发送到RabbitMQ后,RabbitMQ会给生产者返回一个确认消息,告知生产者消息是否成功到达服务器。生产者可以通过confirmSelect()方法开启确认机制,然后通过addConfirmListener()方法添加确认监听器,在监听器中处理确认结果。
      • 事务机制:生产者可以通过txSelect()方法开启事务,然后在发送消息后通过txCommit()方法提交事务,如果发送过程中出现异常,可以通过txRollback()方法回滚事务,确保消息不会丢失。但事务机制会严重影响性能,一般不建议在高并发场景下使用。
    • 队列和消息持久化
      • 队列持久化:通过Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)方法声明队列时,将durable参数设置为true,这样队列会在服务器重启后依然存在。
      • 消息持久化:通过MessageProperties.PERSISTENT_TEXT_PLAIN设置消息的持久化属性,这样消息会被写入磁盘,即使服务器重启也不会丢失。
    • 消费者端
      • 关闭自动确认(auto - ack):消费者在获取消息时,将auto - ack参数设置为false,这样消费者在处理完消息后,需要手动调用basicAck()方法向RabbitMQ确认消息已处理,RabbitMQ才会将消息从队列中删除。如果消费者在处理消息过程中出现异常,没有调用basicAck()方法,RabbitMQ会将消息重新放回队列,保证消息不会丢失。
  9. xxl - job在分布式任务调度中有什么优势
    • 简单易用:xxl - job提供了简洁的Web控制台,用户可以在控制台中方便地进行任务的创建、管理、调度配置等操作,无需复杂的代码开发和配置。
    • 可视化管理:通过Web控制台,用户可以直观地查看任务的执行状态、执行日志、调度记录等信息,方便对任务进行监控和排查问题。
    • 分布式调度:支持将任务分布式部署在多个节点上执行,提高任务处理能力和系统的可用性。同时,它能自动进行故障转移,当某个执行器节点出现故障时,任务会自动分配到其他正常节点执行。
    • 丰富的调度策略:支持多种调度策略,如CRON表达式调度、固定速率调度、固定延迟调度等,满足不同业务场景下的任务调度需求。
    • 弹性扩容缩容:可以根据业务需求方便地增加或减少执行器节点,实现系统的弹性伸缩,提高资源利用率。