《互联网大厂Java求职者面试大揭秘:核心知识与热门框架全考验》

31 阅读2分钟

面试官:第一轮面试开始,首先问你,Java 中多线程的实现方式有哪些?

王铁牛:这个我知道,有继承 Thread 类和实现 Runnable 接口这两种方式。

面试官:回答正确,那说说这两种方式有什么区别呢?

王铁牛:继承 Thread 类的话,一个类就不能再继承其他类了,因为 Java 是单继承嘛。而实现 Runnable 接口就没有这个限制,一个类还可以继承其他类同时实现 Runnable 接口。

面试官:不错,回答得很清晰。那再问你,线程池有哪些核心参数?

王铁牛:好像有 corePoolSize、maximumPoolSize、keepAliveTime 这些。

面试官:嗯,基本答对了。最后一个问题,线程池中的拒绝策略有哪些?

王铁牛:这个……我想想,有 AbortPolicy、CallerRunsPolicy、DiscardPolicy 和 DiscardOldestPolicy。

面试官:第一轮面试结束,整体表现不错。

第二轮面试开始,说说 JVM 内存模型主要分为哪几个部分?

王铁牛:有堆、栈、方法区、程序计数器这些吧。

面试官:那方法区存放哪些数据呢?

王铁牛:主要存放类信息、常量、静态变量等。

面试官:不太准确,方法区还存放运行时常量池等。再问,HashMap 的底层数据结构是什么?

王铁牛:好像是数组和链表。

面试官:不准确,在 JDK1.8 之后是数组+链表+红黑树。第二轮面试结束,有些问题回答得不太准确。

第三轮面试开始,Spring 框架中依赖注入的方式有哪些?

王铁牛:有构造器注入、setter 注入这些。

面试官:那 Spring Boot 自动配置原理是什么?

王铁牛:这个……不太清楚。

面试官:最后问你,MyBatis 中 #{} 和 ${} 的区别是什么?

王铁牛:这个……我真不太知道。

面试官:第三轮面试结束,整体来看你对一些基础知识点掌握还行,但有些复杂问题回答得不太好。回家等通知吧。

答案

  1. Java 多线程实现方式
    • 继承 Thread 类:一个类继承 Thread 类后,重写 run 方法,然后通过创建该类的实例调用 start 方法来启动线程。这种方式的缺点是一个类只能继承一个父类,所以如果已经继承了其他类就不能再继承 Thread 类了。
    • 实现 Runnable 接口:一个类实现 Runnable 接口,重写 run 方法,然后将该类的实例作为参数传递给 Thread 类的构造函数来创建线程对象并启动线程。这种方式的优点是一个类可以同时继承其他类并实现 Runnable 接口,避免了单继承的局限性。
  2. 线程池核心参数
    • corePoolSize:线程池的核心线程数。当提交的任务数小于 corePoolSize 时,线程池会创建新的线程来执行任务。
    • maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于 corePoolSize 时,若任务队列已满,则会创建新的线程来执行任务,直到线程数达到 maximumPoolSize。
    • keepAliveTime:线程池中的线程在空闲时的存活时间。当线程空闲时间超过 keepAliveTime 时,线程会被销毁,直到线程数回到 corePoolSize。
  3. 线程池拒绝策略
    • AbortPolicy:默认的拒绝策略,当线程池无法处理新任务时,会抛出 RejectedExecutionException 异常。
    • CallerRunsPolicy:当线程池无法处理新任务时,会由调用线程(提交任务的线程)直接执行该任务。
    • DiscardPolicy:当线程池无法处理新任务时,直接丢弃该任务,不抛出异常。
    • DiscardOldestPolicy:当线程池无法处理新任务时,丢弃任务队列中最旧的任务,然后尝试重新提交新任务。
  4. JVM 内存模型主要部分
    • :是 JVM 中最大的一块内存区域,用于存储对象实例。几乎所有的对象实例都在这里分配内存。
    • :每个线程都有自己独立的栈空间,主要用于存储局部变量、方法调用等。
    • 方法区:用于存储类信息、常量、静态变量等数据。在 JDK1.8 之后,方法区被元空间(MetaSpace)取代,元空间使用本地内存。
    • 程序计数器:是一块较小的内存区域,它记录着当前线程执行的字节码指令的地址。
  5. HashMap 底层数据结构
    • 在 JDK1.8 之前,HashMap 的底层数据结构是数组 + 链表。当往 HashMap 中插入元素时,首先计算元素的哈希值,然后通过哈希值找到对应的数组位置。如果该位置为空,则直接插入新元素;如果不为空,则将新元素插入到链表的头部。
    • 在 JDK1.8 之后,HashMap 的底层数据结构是数组 + 链表 + 红黑树。当链表长度超过 8 且数组长度大于 64 时,链表会转换为红黑树,以提高查询效率。
  6. Spring 框架依赖注入方式
    • 构造器注入:通过构造函数来注入依赖对象。优点是注入的依赖在对象创建时就已经存在,并且是不可变的;缺点是如果依赖较多,构造函数参数列表会变得很长。
    • setter 注入:通过 setter 方法来注入依赖对象。优点是代码更加灵活,可以在对象创建后再设置依赖;缺点是可能会导致对象在某些状态下处于不一致的状态,因为依赖可以在任何时候被设置。
  7. Spring Boot 自动配置原理
    • Spring Boot 自动配置是基于条件注解实现的。它会根据应用的类路径、配置文件等信息,自动配置合适的 bean。
    • 例如,当类路径下存在某个特定的依赖时,Spring Boot 会自动配置相关的 bean。它通过读取 META-INF/spring.factories 文件中的配置信息,找到对应的自动配置类。这些自动配置类会根据条件判断是否需要创建相应的 bean,并将其注册到 Spring 容器中。
  8. MyBatis 中 #{} 和 ${} 的区别
    • #{}:它会将传入的值作为一个参数,使用 PreparedStatement 进行预编译处理。这样可以有效防止 SQL 注入,因为传入的值会被当成一个参数而不是直接嵌入到 SQL 语句中。例如:SELECT * FROM user WHERE id = #{id}
    • **:它会将传入的值直接嵌入到SQL语句中。这种方式存在SQL注入的风险,例如:SELECTFROMuserWHEREname={}**:它会将传入的值直接嵌入到 SQL 语句中。这种方式存在 SQL 注入的风险,例如:`SELECT * FROM user WHERE name = '{name}'`,如果传入的 name 值包含恶意 SQL 语句,就可能导致数据泄露等安全问题。所以一般情况下应尽量使用 #{},只有在特定场景下(如动态表名等)才考虑使用 ${}。