互联网大厂 Java 面试:核心知识、框架与中间件大考验
在互联网大厂宽敞明亮的面试室里,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。一场对 Java 核心知识、框架和中间件的考验即将展开。
第一轮提问 面试官:首先问几个 Java 基础的问题。Java 中的多态是如何实现的? 王铁牛:这个我知道,Java 多态主要通过继承、接口和方法重写来实现。子类继承父类,重写父类的方法,然后可以用父类的引用指向子类的对象,调用重写后的方法,就能实现多态。 面试官:回答得不错。那说说 JUC 里的 CountDownLatch 有什么作用? 王铁牛:CountDownLatch 可以让一个或多个线程等待其他线程完成操作。比如有一个主线程要等几个子线程都执行完任务后再继续执行,就可以用 CountDownLatch,子线程执行完一个就把计数器减一,主线程等计数器为 0 就接着执行。 面试官:很好。那 JVM 的垃圾回收机制,能简单说下有哪些常见的垃圾收集器吗? 王铁牛:常见的垃圾收集器有 Serial、ParNew、Parallel Scavenge、CMS 和 G1 等。Serial 是单线程的,ParNew 是 Serial 的多线程版本,Parallel Scavenge 主要关注吞吐量,CMS 关注降低停顿时间,G1 是面向大内存的收集器。
第二轮提问 面试官:接下来聊聊多线程和线程池。多线程编程中,如何避免死锁? 王铁牛:要避免死锁,可以按照固定的顺序获取锁,避免一个线程同时获取多个锁。还有就是设置锁的超时时间,如果一段时间内拿不到锁就放弃。 面试官:不错。那说说线程池的核心参数有哪些,分别有什么作用? 王铁牛:线程池的核心参数有 corePoolSize、maximumPoolSize、keepAliveTime、unit 和 workQueue。corePoolSize 是核心线程数,线程池会一直保留这么多线程;maximumPoolSize 是最大线程数,线程不够用的时候可以增加到这个数量;keepAliveTime 是线程空闲的存活时间;unit 是时间单位;workQueue 是任务队列,用来存放待执行的任务。 面试官:回答得挺清晰。那 HashMap 在多线程环境下会有什么问题,如何解决? 王铁牛:HashMap 在多线程环境下会有线程安全问题,可能会导致数据不一致,还可能出现死循环。可以用 ConcurrentHashMap 来替代,它是线程安全的。
第三轮提问 面试官:现在来谈谈框架相关的。Spring 的 IOC 和 AOP 是什么,有什么作用? 王铁牛:IOC 是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器,降低了代码的耦合度。AOP 是面向切面编程,它可以在不修改原有代码的情况下,对程序进行增强,比如实现日志记录、事务管理等。 面试官:说得对。那 Spring Boot 有什么优势,它是如何实现自动配置的? 王铁牛:Spring Boot 的优势就是简化了 Spring 应用的开发,它有很多默认配置,能快速搭建项目。它通过自动配置类和条件注解来实现自动配置,根据类路径下的依赖和配置文件来决定是否启用某些配置。 面试官:最后问一个 MyBatis 的问题,MyBatis 的一级缓存和二级缓存有什么区别? 王铁牛:这个……一级缓存是会话级别的,同一个 SqlSession 里查询相同的数据会从缓存里取。二级缓存是 mapper 级别的,不同的 SqlSession 可以共享。不过具体的细节我有点记不清了。
面试官总结:王铁牛,通过这三轮的提问,我能看出你对 Java 的一些基础知识和框架有一定的了解。在前面两轮,你对多线程、线程池、JUC、JVM 等方面的问题回答得都很不错,展现出了你对这些核心知识的掌握程度。尤其是在回答线程池核心参数和 JUC 里 CountDownLatch 的作用时,解释得很清晰准确,这说明你在日常学习或者工作中对这些内容有深入的研究。
但是在第三轮关于框架和中间件的问题上,你对 Spring 和 Spring Boot 的基础概念回答得还可以,不过在 MyBatis 二级缓存的细节上出现了回答不完整的情况。而且在整个面试过程中,对于一些更深入的技术原理和业务场景的应用,你没有进一步展开阐述,说明你可能在知识的深度和广度上还有所欠缺。
我们后续会综合考虑所有面试者的情况,你先回家等通知吧,大概一周内会给你反馈结果。
问题答案详细解析
- Java 中的多态是如何实现的:
- 多态是 Java 面向对象编程的重要特性之一,它允许不同的对象对同一消息做出不同的响应。主要通过以下几种方式实现:
- 继承:子类继承父类,拥有父类的属性和方法。子类可以重写父类的方法,以实现自己的特定行为。
- 方法重写:子类重新定义父类中已有的方法,方法名、参数列表和返回值类型都要相同,但方法体可以不同。
- 接口:一个类可以实现多个接口,通过接口引用指向实现类的对象,调用接口中定义的方法。
- 父类引用指向子类对象:可以用父类的引用变量指向子类的对象,这样在调用重写的方法时,会根据实际的对象类型来调用相应的方法。
- 多态是 Java 面向对象编程的重要特性之一,它允许不同的对象对同一消息做出不同的响应。主要通过以下几种方式实现:
- JUC 里的 CountDownLatch 有什么作用:
- CountDownLatch 是 Java 并发包(JUC)中的一个同步工具类,它允许一个或多个线程等待其他线程完成操作。
- 它内部维护了一个计数器,在创建 CountDownLatch 对象时需要指定计数器的初始值。当一个线程完成任务后,调用 countDown() 方法将计数器减一。等待的线程可以调用 await() 方法,当计数器的值为 0 时,等待的线程会被唤醒继续执行。
- 例如,在一个多线程的场景中,主线程需要等待多个子线程完成数据加载后再进行后续的处理,就可以使用 CountDownLatch 来实现线程间的同步。
- JVM 的垃圾回收机制,常见的垃圾收集器有哪些:
- Serial 收集器:是最古老的垃圾收集器,它是单线程的,在进行垃圾回收时会暂停所有的用户线程(Stop The World),适用于单 CPU 环境下的小型应用。
- ParNew 收集器:是 Serial 收集器的多线程版本,主要用于配合 CMS 收集器使用,在新生代进行垃圾回收。
- Parallel Scavenge 收集器:也是一个多线程的新生代收集器,它的目标是达到一个可控制的吞吐量,适合在后台运算而不需要太多交互的任务。
- CMS 收集器:(Concurrent Mark Sweep)是以获取最短回收停顿时间为目标的收集器,主要用于老年代的垃圾回收。它的优点是停顿时间短,适合对响应时间要求较高的应用。
- G1 收集器:(Garbage - First)是面向大内存的收集器,它将堆内存划分为多个大小相等的 Region,能预测垃圾回收的停顿时间,并且可以对不同 Region 采用不同的回收策略。
- 多线程编程中,如何避免死锁:
- 避免嵌套锁:一个线程在持有一个锁的同时,不要去尝试获取另一个锁,避免形成锁的嵌套,从而减少死锁的可能性。
- 按顺序获取锁:所有线程都按照相同的顺序获取锁,这样可以避免不同线程以不同的顺序获取锁而导致死锁。
- 设置锁的超时时间:使用可定时的锁,如 ReentrantLock 的 tryLock(long timeout, TimeUnit unit) 方法,如果在指定的时间内没有获取到锁,线程可以放弃等待,避免无限期的等待。
- 死锁检测:可以使用工具或代码来检测死锁的发生,当检测到死锁时,采取相应的措施,如终止某些线程来打破死锁。
- 线程池的核心参数有哪些,分别有什么作用:
- corePoolSize:核心线程数,线程池会一直保持这些线程的存活,即使它们处于空闲状态。当有新任务提交时,如果当前线程数小于 corePoolSize,线程池会创建新的线程来执行任务。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。当任务队列已满,且当前线程数小于 maximumPoolSize 时,线程池会创建新的线程来处理任务。
- keepAliveTime:线程空闲的存活时间,当线程空闲时间超过这个值时,线程会被销毁,直到线程数达到 corePoolSize。
- unit:keepAliveTime 的时间单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
- workQueue:任务队列,用于存放待执行的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
- HashMap 在多线程环境下会有什么问题,如何解决:
- 线程安全问题:HashMap 不是线程安全的,在多线程环境下,多个线程同时对 HashMap 进行读写操作,可能会导致数据不一致的问题,比如数据丢失、覆盖等。
- 死循环问题:在 JDK 1.7 及以前,当多个线程同时进行扩容操作时,可能会形成环形链表,导致后续的查询操作陷入死循环。
- 解决方法:可以使用 ConcurrentHashMap 来替代 HashMap,ConcurrentHashMap 是线程安全的,它采用了分段锁(JDK 1.7)或 CAS 和 synchronized(JDK 1.8)的方式来保证线程安全。
- Spring 的 IOC 和 AOP 是什么,有什么作用:
- IOC(控制反转):是一种设计模式,它将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。在传统的编程中,对象的创建和依赖关系是在代码中硬编码的,而在 IOC 中,对象的创建和依赖关系由 Spring 容器负责。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。
- AOP(面向切面编程):是一种编程范式,它允许在不修改原有代码的情况下,对程序进行增强。AOP 通过将横切关注点(如日志记录、事务管理、权限验证等)从业务逻辑中分离出来,以切面的形式进行统一管理。这样可以提高代码的复用性,减少代码的重复。
- Spring Boot 有什么优势,它是如何实现自动配置的:
- 优势:
- 简化配置:Spring Boot 提供了大量的默认配置,减少了开发者编写配置文件的工作量。
- 快速搭建项目:通过 Spring Initializr 可以快速生成一个基本的 Spring Boot 项目,集成了各种常用的依赖。
- 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,无需单独部署服务器。
- 监控和管理:提供了丰富的监控和管理端点,方便对应用进行监控和管理。
- 自动配置原理:Spring Boot 通过自动配置类和条件注解来实现自动配置。在启动时,Spring Boot 会扫描类路径下的所有自动配置类,这些自动配置类会根据类路径下的依赖和配置文件来判断是否启用某些配置。例如,如果类路径下存在 MySQL 的驱动,Spring Boot 会自动配置数据源。条件注解如 @ConditionalOnClass、@ConditionalOnMissingBean 等可以根据不同的条件来决定是否加载某个配置类。
- 优势:
- MyBatis 的一级缓存和二级缓存有什么区别:
- 一级缓存:是会话级别的缓存,它存在于一个 SqlSession 中。当同一个 SqlSession 多次执行相同的查询语句时,第一次查询会将结果存入缓存,后续的查询会直接从缓存中获取数据,而不需要再次访问数据库。当 SqlSession 关闭或提交事务时,一级缓存会被清空。
- 二级缓存:是 mapper 级别的缓存,它存在于多个 SqlSession 之间,可以被多个 SqlSession 共享。二级缓存需要在 MyBatis 的配置文件中进行配置,并且需要在 mapper 文件中开启。当一个 SqlSession 执行查询操作后,会将结果存入二级缓存,其他 SqlSession 也可以从二级缓存中获取相同的数据。二级缓存的作用域更大,但是需要注意缓存的一致性问题。