《互联网大厂Java求职者面试大揭秘:核心知识、框架与中间件考察全记录》

60 阅读5分钟

面试官:第一轮提问开始。首先,简单说一下Java中的多线程有哪些实现方式?

王铁牛:嗯……有继承Thread类和实现Runnable接口这两种方式。

面试官:不错,回答正确。那再问一个,说说线程池的几个重要参数及其作用。

王铁牛:这个我知道,corePoolSize是核心线程数,当提交的任务数小于它时,会创建新线程执行任务;maximumPoolSize是最大线程数,当任务数超过核心线程数且队列满时,会创建新线程直到达到这个数;keepAliveTime是线程存活时间,当线程数超过核心线程数,空闲线程会在这个时间后销毁;unit是时间单位;workQueue是任务队列,用来存放提交的任务;handler是拒绝策略,当任务数超过最大线程数且队列满时,用来处理新提交的任务。

面试官:回答得很清晰,值得夸赞。下面进入第二轮提问。请讲讲HashMap的底层实现原理。

王铁牛:它是基于数组和链表实现的,当哈希冲突时会将新节点添加到链表末尾。后来在JDK1.8中引入了红黑树,当链表长度超过8时会转换为红黑树来提高查询效率。

面试官:嗯,了解。那Spring框架中,IoC和AOP的概念是什么?

王铁牛:IoC就是控制反转,把对象的创建和依赖关系管理交给Spring容器;AOP是面向切面编程,通过动态代理在不修改原有代码的基础上增强功能。

面试官:还算说得过去。最后一轮提问,Dubbo的集群容错策略有哪些?

王铁牛:这个……好像有什么失败重试、负载均衡啥的,具体的我也不太清楚了。

面试官:好了,面试就到这里,回去等通知吧。

答案:

  1. Java多线程实现方式
    • 继承Thread类:创建一个类继承Thread类,重写run方法,在run方法中编写线程执行的代码。然后通过创建该类的实例并调用start方法来启动线程。例如:
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("线程执行的代码");
        }
    }
    MyThread thread = new MyThread();
    thread.start();
    
    • 实现Runnable接口:创建一个类实现Runnable接口,重写run方法。然后通过创建Thread类的实例并将实现Runnable接口的类的实例作为参数传递给Thread的构造函数,最后调用start方法启动线程。例如:
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("线程执行的代码");
        }
    }
    MyRunnable runnable = new MyRunnable();
    Thread thread = new Thread(runnable);
    thread.start();
    
  2. 线程池参数及其作用
    • corePoolSize:核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新线程来执行任务。这些线程会一直存在,除非设置了allowCoreThreadTimeOut为true。
    • maximumPoolSize:最大线程数。当任务数超过corePoolSize且任务队列已满时,线程池会创建新线程直到线程数达到maximumPoolSize。如果超过这个数,任务会根据拒绝策略进行处理。
    • keepAliveTime:线程存活时间。当线程数超过corePoolSize,空闲线程会在keepAliveTime指定的时间后销毁,前提是设置了allowCoreThreadTimeOut为true或者线程池采用了非核心线程池模式(默认)。
    • unit:keepAliveTime的时间单位,例如TimeUnit.SECONDS表示秒。
    • workQueue:任务队列,用来存放提交的任务。常用的有ArrayBlockingQueue(有界阻塞队列)、LinkedBlockingQueue(无界阻塞队列)等。
    • handler:拒绝策略。当任务数超过maximumPoolSize且队列满时,用来处理新提交的任务。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(调用者运行任务)、DiscardPolicy(丢弃新提交的任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  3. HashMap底层实现原理
    • 数组和链表:HashMap底层是一个数组,每个数组元素是一个链表节点(在JDK1.8之前)。当向HashMap中插入键值对时,首先通过哈希函数计算键的哈希值,然后根据哈希值找到对应的数组下标。如果该下标为空,则直接插入新节点;如果不为空,则遍历链表,找到相同键的节点则更新其值,否则在链表末尾添加新节点。
    • 红黑树:在JDK1.8中,当链表长度超过8时,链表会转换为红黑树。这是因为链表在查找元素时时间复杂度为O(n),而红黑树的时间复杂度为O(logn),可以提高查询效率。当链表长度小于6时,红黑树又会转换回链表。
  4. Spring框架中IoC和AOP的概念
    • IoC(控制反转):传统的对象创建和依赖关系管理由程序员手动完成,而IoC把这些工作交给Spring容器。Spring容器通过配置文件或注解等方式创建对象,并管理对象之间的依赖关系。例如,一个类依赖另一个类,在IoC模式下,Spring容器会自动创建并注入依赖对象,而不是在类内部手动创建。
    • AOP(面向切面编程):通过动态代理机制,在不修改原有业务代码的基础上,为业务方法添加额外的功能,如日志记录、事务管理等。这些额外的功能被称为切面。AOP将横切关注点(如日志、事务)与业务逻辑分离,提高了代码的可维护性和复用性。
  5. Dubbo的集群容错策略
    • 失败重试:当调用失败时,自动重新发起调用,直到成功或达到重试次数上限。可以通过配置retries参数来设置重试次数。
    • 负载均衡:在多个服务提供者之间选择一个进行调用。常见的负载均衡策略有随机、轮询、加权随机、加权轮询等。可以通过配置loadbalance参数来选择负载均衡策略。
    • 集群容错策略还有
      • failfast:快速失败,只发起一次调用,失败立即报错。
      • failsafe:失败安全,出现异常时直接忽略,不抛出异常。
      • forking:并行调用多个服务提供者,只要有一个成功就返回。可以通过配置forks参数来设置并行调用的服务提供者数量。
      • broadcast:广播调用所有服务提供者,逐个调用,任意一个报错则报错。