面试官:请简要介绍一下Java核心知识,比如面向对象的三大特性。
王铁牛:嗯,面向对象的三大特性就是封装、继承和多态。封装就是把数据和操作数据的方法封装在一起;继承就是子类继承父类的属性和方法;多态就是同一个行为具有多个不同表现形式。
面试官:不错,回答得很清晰。那再说说JUC里的CountDownLatch的作用。
王铁牛:CountDownLatch可以用来实现线程间的同步,它允许一个或多个线程等待其他线程完成一组操作之后再继续执行。
面试官:很好。接下来谈谈JVM的垃圾回收机制。
王铁牛:这个嘛,垃圾回收机制就是自动回收不再使用的内存空间,主要有标记清除、标记整理、复制算法等几种方式。
第一轮结束。
面试官:讲讲多线程中线程安全的问题以及如何解决。
王铁牛:线程安全问题就是多个线程同时访问共享资源可能导致数据不一致,解决方法有使用锁、并发容器等。
面试官:那线程池的核心参数有哪些,分别有什么作用?
王铁牛:线程池的核心参数有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue和threadFactory。corePoolSize是核心线程数,maximumPoolSize是最大线程数,keepAliveTime是线程存活时间,unit是时间单位,workQueue是任务队列,threadFactory是线程工厂。
面试官:说说HashMap的底层实现原理。
王铁牛:HashMap底层是数组+链表+红黑树的结构,通过哈希值来确定元素的存储位置。
第二轮结束。
面试官:Spring框架中依赖注入有几种方式?
王铁牛:有构造器注入、setter方法注入、接口注入等方式。
面试官:那Spring Boot的自动配置原理是什么?
王铁牛:Spring Boot的自动配置原理就是通过@EnableAutoConfiguration注解,根据类路径下的依赖来自动配置Spring容器。
面试官:讲讲MyBatis的缓存机制。
王铁牛:MyBatis有一级缓存和二级缓存,一级缓存是SqlSession级别的,二级缓存是namespace级别的。
面试官:最后问问Dubbo的服务注册与发现原理。
王铁牛:Dubbo通过Zookeeper等注册中心来实现服务的注册与发现,服务提供者将服务信息注册到注册中心,服务消费者从注册中心获取服务信息。
面试结束,面试官让王铁牛回家等通知。
答案:
- 面向对象的三大特性:
- 封装:将数据和操作数据的方法封装在一起,对外提供统一的接口。这样可以隐藏内部实现细节,提高数据的安全性和程序的可维护性。例如,在一个类中定义私有属性和公共的getter/setter方法来访问和修改属性。
- 继承:子类继承父类的属性和方法,实现代码的复用。子类可以扩展父类的功能,同时避免重复编写相同的代码。比如一个父类定义了一些通用的方法,子类可以继承这些方法并根据自身需求进行扩展。
- 多态:同一个行为具有多个不同表现形式。可以通过方法重写和接口实现来实现多态。例如,一个父类类型的变量可以指向其子类的对象,调用同一个方法时会根据对象的实际类型执行不同的操作。
- CountDownLatch的作用: 它允许一个或多个线程等待其他线程完成一组操作之后再继续执行。例如,有多个线程需要完成一些初始化工作,然后主线程需要等待所有初始化工作完成后再继续执行后续逻辑,就可以使用CountDownLatch。在初始化线程完成工作后调用countDown()方法递减计数器,当计数器减为0时,等待的线程就可以继续执行。
- JVM的垃圾回收机制:
- 标记清除算法:首先标记出所有需要回收的对象,然后统一回收掉所有被标记的对象。这种算法的缺点是会产生大量不连续的内存碎片。
- 标记整理算法:先标记出需要回收的对象,然后将存活的对象向一端移动,最后清理掉端边界以外的内存。解决了内存碎片问题,但移动对象的开销较大。
- 复制算法:将内存分为两块,每次只使用其中一块。当这一块内存使用完后,将存活的对象复制到另一块内存,然后清理原来的内存块。适用于对象存活率较低的场景,效率较高,但浪费了一半的内存空间。
- 多线程中线程安全问题及解决方法:
- 线程安全问题:多个线程同时访问共享资源时,可能会导致数据不一致、竞争条件等问题。例如多个线程同时对一个共享变量进行读写操作。
- 解决方法:
- 使用锁:如synchronized关键字,它可以保证同一时刻只有一个线程能访问被其修饰的代码块或方法。也可以使用ReentrantLock等显式锁,提供了更灵活的锁控制。
- 并发容器:如ConcurrentHashMap,它是线程安全的哈希表,在多线程环境下可以高效地进行读写操作。
- 线程池的核心参数及作用:
- corePoolSize:核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
- maximumPoolSize:最大线程数。当提交的任务数大于corePoolSize且任务队列已满时,会创建新的线程,直到线程数达到maximumPoolSize。
- keepAliveTime:线程存活时间。当线程数大于corePoolSize时,多余的线程在空闲一段时间后会被销毁,这段空闲时间就是keepAliveTime。
- unit:时间单位,与keepAliveTime配合使用,指定存活时间的单位,如秒、分钟等。
- workQueue:任务队列。用于存放提交的任务,当线程池忙时,任务会被放入队列中等待执行。
- threadFactory:线程工厂。用于创建线程,可自定义线程的名称、优先级等属性。
- HashMap的底层实现原理:
HashMap底层是数组+链表+红黑树的结构。当一个键值对插入时,首先通过哈希函数计算键的哈希值,然后根据哈希值确定在数组中的存储位置。
- 如果该位置为空,则直接插入新的键值对。
- 如果该位置不为空,则会比较键的哈希值和已存在键的哈希值,如果相等且键的equals方法返回true,则更新对应的值。
- 如果哈希值相等但键不相等,则会形成链表或红黑树(当链表长度达到一定阈值时会转换为红黑树),新的键值对会插入到链表或红黑树中。
- Spring框架中依赖注入的方式:
- 构造器注入:通过构造函数来注入依赖对象。优点是注入的依赖在对象创建时就已经确定,且必须提供所有依赖才能创建对象,有助于保证对象的完整性。例如:public class UserService { private final Dao dao; public UserService(Dao dao) { this.dao = dao; } }
- setter方法注入:通过setter方法来注入依赖对象。优点是更加灵活,可以在对象创建后再设置依赖,也可以方便地进行属性的修改。例如:public class UserService { private Dao dao; public void setDao(Dao dao) { this.dao = dao; } }
- 接口注入:通过实现特定的接口来注入依赖对象,这种方式使用较少。
- Spring Boot的自动配置原理: Spring Boot的自动配置原理是通过@EnableAutoConfiguration注解来实现的。它会根据类路径下的依赖来自动配置Spring容器。当应用启动时,Spring Boot会扫描classpath下的所有jar包和类,然后根据这些依赖来推断需要自动配置哪些组件。例如,如果引入了Spring Data JPA的依赖,Spring Boot会自动配置JPA相关的组件,包括数据源、EntityManagerFactory等。它会根据一些默认的配置类和条件注解来决定是否启用某个自动配置类,从而实现自动配置的功能。
- MyBatis的缓存机制:
- 一级缓存:是SqlSession级别的缓存。在同一个SqlSession中,当执行相同的查询时,会直接从缓存中获取结果,而不会再次查询数据库。一级缓存的生命周期与SqlSession相同,当SqlSession关闭时,一级缓存也会被清空。
- 二级缓存:是namespace级别的缓存。多个SqlSession可以共享二级缓存。当一个SqlSession查询数据时,首先会在一级缓存中查找,如果没有找到,会在二级缓存中查找,然后再查询数据库。二级缓存的生命周期更长,默认情况下,只有当namespace中的所有Mapper接口对应的SqlSession都关闭时,二级缓存才会被清空。
- Dubbo的服务注册与发现原理:
Dubbo通过Zookeeper等注册中心来实现服务的注册与发现。
- 服务注册:服务提供者启动时,会将自身的服务信息(如服务接口、实现类、服务地址等)封装成一个ServiceInstance对象,然后通过Dubbo的注册中心客户端将该对象注册到注册中心。例如,服务提供者将UserService的实现类和对应的IP地址、端口等信息注册到Zookeeper中。
- 服务发现:服务消费者启动时,会从注册中心订阅自己需要的服务信息。当服务提供者的服务信息发生变化(如服务地址变更、服务下线等),注册中心会通知服务消费者。服务消费者根据订阅到的服务信息来调用服务提供者的服务。比如服务消费者通过Dubbo的注册中心客户端从Zookeeper中获取UserService的服务地址,然后通过网络调用服务提供者提供的服务。