面试官:请简要介绍一下Java中的多线程,包括线程的创建方式和生命周期。
王铁牛:线程创建可以通过继承Thread类或者实现Runnable接口。生命周期包括新建、就绪、运行、阻塞和死亡。
面试官:不错,回答得挺清晰。那说说线程池的核心参数都有哪些,以及它们的作用。
王铁牛:核心参数有corePoolSize、maximumPoolSize、keepAliveTime、unit和workQueue。corePoolSize是核心线程数,maximumPoolSize是最大线程数,keepAliveTime是线程池中的线程在超出corePoolSize之后,多余的空闲线程的存活时间,unit是时间单位,workQueue是任务队列。
面试官:很好。再问一个,JVM的内存区域是如何划分的?
王铁牛:JVM内存区域分为堆、栈、方法区、程序计数器、本地方法栈。堆是存放对象实例的地方,栈是存放局部变量和方法调用的地方,方法区存放类信息、常量、静态变量等,程序计数器记录当前线程执行的字节码指令地址,本地方法栈用于执行本地方法。
第一轮结束
面试官:接下来谈谈HashMap的底层实现原理。
王铁牛:HashMap底层是数组+链表+红黑树。当链表长度超过一定阈值时会转换为红黑树。
面试官:那ArrayList和LinkedList的区别是什么?
王铁牛:ArrayList是基于数组实现,随机访问快,插入删除慢;LinkedList是基于链表实现,随机访问慢,插入删除快。
面试官:Spring框架中,依赖注入有哪些方式?
王铁牛:有构造器注入、setter方法注入。
第二轮结束
面试官:说说Spring Boot的自动配置原理。
王铁牛:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,它会根据类路径下的依赖自动配置相关的Bean。
面试官:MyBatis的缓存机制了解吗?
王铁牛:有一级缓存和二级缓存,一级缓存是SqlSession级别的,二级缓存是namespace级别的。
面试官:Dubbo的服务注册与发现是如何实现的?
王铁牛:Dubbo通过注册中心实现服务的注册与发现,比如Zookeeper等。
第三轮结束
面试结束后,面试官表示会让王铁牛回家等通知。王铁牛在面试中对一些简单问题回答得不错,展现了一定的基础知识,但对于复杂问题的回答存在明显不足,在技术深度和理解的准确性上还有待提高。整体来看,他虽然有一定的基础,但还需要进一步深入学习和钻研相关技术,才能更好地应对互联网大厂的面试要求。
答案:
- 多线程:
- 线程创建方式:
- 继承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实例并启动线程。例如:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行的任务");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
- 线程生命周期:
- 新建(New):当使用new关键字创建一个线程对象后,该线程就处于新建状态,此时它还没有开始运行。
- 就绪(Runnable):新建的线程调用start方法后,进入就绪状态。此时线程具备了运行的条件,但还没有获取到CPU时间片,需要等待CPU调度。
- 运行(Running):当线程获得CPU时间片后,就进入运行状态,开始执行run方法中的代码。
- 阻塞(Blocked):线程在运行过程中可能会因为某些原因进入阻塞状态,比如调用了sleep方法、等待锁、I/O操作等。处于阻塞状态的线程不会占用CPU资源,直到阻塞解除。
- 死亡(Dead):当线程的run方法执行完毕或者调用了stop方法等,线程就进入死亡状态,结束生命周期。
- 线程池核心参数:
- corePoolSize:核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
- maximumPoolSize:最大线程数。当提交的任务数大于corePoolSize且任务队列已满时,线程池会创建新的线程来执行任务,但线程数不会超过maximumPoolSize。
- keepAliveTime:线程池中的线程在超出corePoolSize之后,多余的空闲线程的存活时间。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列。用于存放提交到线程池但尚未执行的任务。当提交的任务数大于corePoolSize时,任务会被放入workQueue中。常见的任务队列有ArrayBlockingQueue、LinkedBlockingQueue等。
- JVM内存区域划分:
- 堆(Heap):是JVM中最大的内存区域,用于存放对象实例。所有的对象实例都在堆中分配内存。堆可以分为新生代、老年代和永久代(Java 8后为元空间)。新生代又分为Eden区和两个Survivor区。
- 栈(Stack):每个线程都有自己独立的栈空间。栈中主要存放局部变量、方法调用等信息。当方法被调用时,会在栈中压入一个栈帧,栈帧中包含局部变量表、操作数栈、动态链接等信息。方法执行完毕后,栈帧会被弹出。
- 方法区(Method Area):用于存放类信息、常量、静态变量等。在Java 8之前,方法区也被称为永久代,它是一块固定大小的内存区域。Java 8后,永久代被元空间取代,元空间使用的是本地内存,大小不受JVM参数限制。
- 程序计数器(Program Counter Register):记录当前线程执行的字节码指令地址。它是线程私有的,每个线程都有自己独立的程序计数器。
- 本地方法栈(Native Method Stack):用于执行本地方法(通过JNI调用的C或C++方法)。
- HashMap底层实现原理:
- HashMap底层是基于数组和链表(JDK 8后引入红黑树)实现的。
- 数组:HashMap内部维护了一个Entry数组,数组的每个元素是一个链表(JDK 8后可能是链表或者红黑树)的头节点。当插入一个键值对时,会通过计算键的哈希值,然后对数组长度取模,得到一个索引位置,将键值对存储在该索引位置对应的链表或红黑树中。
- 链表:当两个或多个键的哈希值通过取模后得到相同的索引位置时,这些键值对会存储在同一个链表中。链表的插入是头插法,新插入的节点会放在链表的头部。
- 红黑树:当链表长度超过一定阈值(默认8)时,链表会转换为红黑树。红黑树是一种自平衡二叉查找树,相比于链表,它可以提高查找、插入和删除操作的效率。
- ArrayList和LinkedList区别:
- ArrayList:
- 基于数组实现:内部通过数组来存储元素,所以它具有数组的特点。
- 随机访问快:由于元素在内存中是连续存储的,所以可以通过下标直接计算出元素的内存地址,随机访问效率高。
- 插入删除慢:当进行插入或删除操作时,需要移动大量的元素,效率较低。例如在中间位置插入元素,需要将插入位置后面的所有元素向后移动一位。
- LinkedList:
- 基于链表实现:内部通过节点来存储元素,每个节点包含数据和指向下一个节点的引用。
- 随机访问慢:要访问链表中的某个元素,需要从头开始遍历链表,直到找到目标元素,效率较低。
- 插入删除快:在链表中进行插入或删除操作,只需要修改相关节点的引用即可,不需要移动大量元素,效率较高。例如在中间位置插入元素,只需要修改插入位置前后节点的引用。
- ArrayList:
- Spring框架依赖注入方式:
- 构造器注入:通过构造函数来注入依赖。在类的构造函数中定义需要注入的依赖对象,Spring会在创建对象时自动将这些依赖对象注入进来。例如:
class MyClass {
private Dependency dependency;
public MyClass(Dependency dependency) {
this.dependency = dependency;
}
}
- setter方法注入:通过类的setter方法来注入依赖。先定义一个setter方法,然后Spring会在创建对象后,通过调用该setter方法将依赖对象注入进来。例如:
class MyClass {
private Dependency dependency;
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
- Spring Boot自动配置原理: Spring Boot的自动配置是基于@EnableAutoConfiguration注解实现的。 当一个Spring Boot应用启动时,@EnableAutoConfiguration会被触发。它会根据类路径下的依赖情况,自动配置相关的Bean。 具体来说,Spring Boot会扫描类路径下的所有jar包,找到那些符合特定条件的自动配置类(通常以AutoConfiguration结尾)。这些自动配置类会根据应用的依赖情况,自动配置相应的Bean。例如,如果应用中引入了Spring Data JPA依赖,那么Spring Boot会自动配置Jpa相关的Bean,如EntityManagerFactory、JpaRepository等。自动配置类中会使用条件注解(如@Conditional)来决定是否配置某个Bean。比如只有在类路径下存在特定的库时,才会配置相应的数据源Bean等。这样就实现了根据应用的实际依赖自动配置各种Bean,极大地简化了Spring应用的配置过程。
- MyBatis缓存机制:
- 一级缓存:
- 一级缓存是SqlSession级别的缓存。当执行一个查询时,MyBatis会先在当前的SqlSession中查找缓存。
- 如果缓存中存在该查询结果,直接返回缓存中的数据;如果不存在,则执行数据库查询,并将查询结果放入缓存中。
- 当同一个SqlSession执行相同的查询时,会直接从缓存中获取数据,不会再次查询数据库。
- 当SqlSession关闭时,一级缓存会被清空。
- 二级缓存:
- 二级缓存是namespace级别的缓存。一个namespace下的所有操作共享一个二级缓存。
- 开启二级缓存后,MyBatis会将查询结果放入二级缓存中。当不同的SqlSession执行相同的查询时,会先从二级缓存中获取数据。
- 二级缓存的有效期更长,除非手动清空或者应用重启等情况,否则数据会一直存在。
- 要使用二级缓存,需要在MyBatis的配置文件中开启,并在Mapper.xml文件中设置cache标签来启用二级缓存。
- 一级缓存:
- Dubbo服务注册与发现实现:
Dubbo通过注册中心实现服务的注册与发现。常见的注册中心有Zookeeper、Nacos等。
- 服务注册:
- 当一个Dubbo服务启动时,服务提供方会将自己的服务信息(如服务接口、实现类、服务地址等)发送到注册中心。
- 注册中心会将这些服务信息存储起来,供其他服务消费者查询。
- 服务发现:
- 服务消费者启动时,会向注册中心订阅自己需要的服务。
- 注册中心会实时监听服务的变化情况,当有新的服务提供方注册或者已有服务提供方的服务信息发生变化时,注册中心会通知服务消费者。
- 服务消费者根据注册中心提供的服务信息,选择合适的服务提供方进行调用。这样就实现了服务的动态发现和调用。
- 服务注册: