《互联网大厂Java求职者面试:核心知识大考验》

34 阅读5分钟

面试官:第一轮面试开始。首先问你,Java 中如何创建一个线程?

王铁牛:可以通过继承 Thread 类或者实现 Runnable 接口来创建线程。

面试官:不错,回答正确。那再问你,什么是线程池?它有什么作用?

王铁牛:线程池就是预先创建一定数量的线程,当有任务到来时,从线程池中获取线程来执行任务,执行完任务后线程不会销毁,而是继续留在线程池中等待下一个任务。它可以提高线程的复用性,减少线程创建和销毁的开销。

面试官:很好,回答得很清晰。最后一个问题,HashMap 在多线程环境下会出现什么问题?

王铁牛:嗯……会出现链表成环,然后导致死循环吧。

面试官:第二轮面试。说说 JVM 的内存结构主要有哪些?

王铁牛:有堆、栈、方法区这些。

面试官:那堆内存又分为哪几个区域?

王铁牛:好像有新生代、老年代、永久代。

面试官:永久代现在在 Java 8 中叫什么了?

王铁牛:呃……不太记得了,好像叫元空间。

面试官:第三轮面试。讲讲 Spring 的核心特性有哪些?

王铁牛:嗯……能依赖注入?

面试官:Spring Boot 相对于 Spring 有哪些优势?

王铁牛:这个,Spring Boot 更简单,更方便搭建项目吧。

面试官:MyBatis 的工作原理是什么?

王铁牛:就是通过 XML 配置文件或者注解来映射 SQL 语句。

面试结束,总体来看,你对一些简单的 Java 核心知识有一定了解,但对于复杂点的问题回答得不是很全面清晰。回去等通知吧。

答案:

  1. Java 创建线程的方式
    • 继承 Thread 类:定义一个类继承 Thread 类,重写 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. 线程池
    • 线程池是一种多线程处理形式,它预先创建一定数量的线程,当有任务到来时,从线程池中获取线程来执行任务,执行完任务后线程不会销毁,而是继续留在线程池中等待下一个任务。
    • 作用:
      • 提高线程复用性:避免频繁创建和销毁线程,减少系统开销。
      • 控制线程数量:可以根据系统资源情况合理设置线程池大小,防止线程过多导致系统资源耗尽。
      • 便于管理:统一管理线程,方便监控和统计线程的使用情况。
  3. HashMap 在多线程环境下的问题
    • 在多线程环境下,HashMap 可能会出现链表成环导致死循环的问题。这是因为在扩容时,如果多个线程同时对 HashMap 进行操作,可能会导致链表节点的顺序错乱,形成环形链表。当后续进行查找等操作时,就可能陷入死循环。
    • 例如在 JDK 1.7 及之前的版本中,HashMap 的扩容机制存在问题。当进行扩容时,会重新计算节点的位置并插入到新的数组中。在这个过程中,如果有多个线程同时操作,就可能出现链表成环的情况。
  4. JVM 的内存结构
    • :是 JVM 中最大的一块内存区域,用于存储对象实例。堆又分为新生代、老年代、永久代(Java 8 中叫元空间)。
    • :每个线程都有自己独立的栈空间,用于存储局部变量、方法调用等信息。
    • 方法区:存储类信息、常量、静态变量等。在 Java 8 中,永久代被元空间取代,元空间使用本地内存,而不是像永久代那样使用 JVM 堆内存。
  5. Spring 的核心特性
    • 依赖注入:通过 XML 配置文件、注解等方式将对象之间的依赖关系注入到对象中,降低对象之间的耦合度。例如:
    <bean id="userService" class="com.example.UserService">
        <constructor-arg ref="userDao"/>
    </bean>
    <bean id="userDao" class="com.example.UserDao"/>
    
    或者使用注解:
    @Component
    public class UserService {
        @Autowired
        private UserDao userDao;
    }
    @Repository
    public class UserDao {
    }
    
    • IoC(控制反转):将对象的创建和管理交给 Spring 容器,而不是在对象内部自行创建和管理依赖对象。
    • 面向切面编程(AOP):可以在不修改原有代码的基础上,对业务逻辑进行增强,比如日志记录、事务管理等。例如:
    @Aspect
    public class LogAspect {
        @Before("execution(* com.example.service.*.*(..))")
        public void logBefore() {
            System.out.println("方法执行前记录日志");
        }
    }
    
  6. Spring Boot 相对于 Spring 的优势
    • 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,通过自动配置可以快速搭建一个完整的项目,减少了繁琐的配置工作。
    • 约定大于配置:遵循一定的约定,减少了不必要的 XML 配置或者注解配置,使项目结构更加清晰,开发效率更高。
    • 内置 Web 容器:内置了 Tomcat 等 Web 容器,无需像传统 Spring 项目那样手动配置和部署 Web 容器。
  7. MyBatis 的工作原理
    • 配置文件:通过 XML 配置文件或者注解来映射 SQL 语句。在 XML 配置文件中,可以定义 SQL 语句、参数映射、结果集映射等。例如:
    <select id="getUserById" parameterType="int" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
    • SQL 执行:MyBatis 通过 SqlSessionFactory 创建 SqlSession,SqlSession 负责执行 SQL 语句。当调用 SqlSession 的方法时,会根据配置文件中的映射信息来执行相应的 SQL 操作,并将结果进行处理返回给调用者。
    • 缓存机制:MyBatis 有一级缓存和二级缓存。一级缓存是 SqlSession 级别的,在同一个 SqlSession 中执行相同的 SQL 语句时,会直接从缓存中获取结果。二级缓存是基于 namespace 的,多个 SqlSession 可以共享二级缓存。