面试官:请简要介绍一下 Java 核心知识中的面向对象编程概念,比如封装、继承和多态。
王铁牛:封装就是把数据和操作数据的方法封装在一起,对外提供统一的接口。继承是子类继承父类的属性和方法。多态就是同一个行为具有多个不同表现形式或形态。
面试官:回答得不错。那说说 JUC 包中常用的并发工具类有哪些?
王铁牛:有 CountDownLatch、CyclicBarrier、Semaphore 这些。
面试官:嗯,还可以。再问你,JVM 的内存结构主要分为哪几个部分?
王铁牛:有堆、栈、方法区、程序计数器、本地方法栈。
第一轮结束
面试官:接下来问多线程相关的。如何创建一个线程?
王铁牛:可以继承 Thread 类,重写 run 方法;也可以实现 Runnable 接口,实现 run 方法;还可以实现 Callable 接口,通过 FutureTask 来包装。
面试官:线程池的核心参数有哪些?
王铁牛:有 corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
面试官:那 HashMap 在多线程环境下会出现什么问题?
王铁牛:会出现数据丢失、死循环等问题。
第二轮结束
面试官:说说 Spring 框架中 IOC 和 AOP 的原理。
王铁牛:IOC 就是控制反转,把对象的创建和依赖注入交给 Spring 容器。AOP 是面向切面编程,通过动态代理实现增强功能。
面试官:Spring Boot 有哪些优点?
王铁牛:配置简单、快速构建项目、内置 Tomcat 等。
面试官:MyBatis 的核心配置文件主要配置哪些内容?
王铁牛:有数据源、事务管理器、映射器等配置。
第三轮结束
面试结束,面试官表示会综合评估并让王铁牛回家等通知。
答案:
- Java 面向对象编程概念:
- 封装:将数据和操作数据的方法绑定在一起,通过访问修饰符控制外界对数据的访问,实现数据的隐藏和安全保护。例如,一个类中的私有成员变量只能通过该类提供的公共方法来访问和修改。
- 继承:子类继承父类的属性和方法,实现代码的复用。子类可以扩展和重写父类的方法。比如,一个 Animal 类有 eat 方法,Dog 类继承自 Animal 类,就可以复用 eat 方法,也可以根据狗的特点重写 eat 方法。
- 多态:同一个行为具有多个不同表现形式或形态。可以通过方法重载(在同一个类中)和方法重写(在继承关系中)来实现。比如,一个父类类型的变量可以指向其子类的对象,调用该变量的方法时,实际执行的是子类重写后的方法。
- JUC 包中常用并发工具类:
- CountDownLatch:允许一个或多个线程等待其他线程完成操作。例如,有多个线程需要等待某个初始化操作完成后再继续执行,可以使用 CountDownLatch 来实现。初始化时设置一个计数值,每个线程完成相关操作后调用 countDown 方法减少计数值,当计数值为 0 时,等待的线程可以继续执行。
- CyclicBarrier:让一组线程互相等待,直到所有线程都到达某个屏障点。它可以循环使用,不像 CountDownLatch 只能使用一次。比如,在进行团队合作任务时,需要所有成员都准备好后才能一起开始执行后续操作,就可以使用 CyclicBarrier。
- Semaphore:控制同时访问某个资源的线程数量。例如,限制对数据库连接池的并发访问数量,通过设置 Semaphore 的许可数量来实现。线程获取许可才能访问资源,访问完后释放许可。
- JVM 内存结构:
- 堆:是 JVM 中最大的内存区域,用于存储对象实例。所有对象都在堆中分配内存。堆又可以分为新生代、老年代和永久代(Java 8 后为元空间)。新生代主要用于存放新创建的对象,老年代存放经过多次垃圾回收后仍然存活的对象,永久代(元空间)用于存储类信息、常量等。
- 栈:每个线程都有自己的栈,用于存储局部变量、方法调用等。栈中的数据遵循先进后出的原则。
- 方法区:存储类信息、常量、静态变量等。它是各个线程共享的内存区域。
- 程序计数器:记录当前线程正在执行的字节码指令地址,是线程私有的。
- 本地方法栈:用于执行本地方法(用 C、C++ 等编写的方法)。
- 创建线程的方式:
- 继承 Thread 类:定义一个类继承自 Thread 类,重写 run 方法,然后创建该类的实例并调用 start 方法启动线程。例如:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
MyThread thread = new MyThread();
thread.start();
- **实现 Runnable 接口**:定义一个类实现 Runnable 接口,实现 run 方法,然后将该类实例作为参数传递给 Thread 类的构造函数创建线程对象并启动。例如:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
- **实现 Callable 接口**:定义一个类实现 Callable 接口,实现 call 方法(与 run 方法不同,call 方法有返回值),通过 FutureTask 来包装该类实例,然后将 FutureTask 作为参数传递给 Thread 类的构造函数创建线程对象并启动。最后可以通过 FutureTask 的 get 方法获取 call 方法的返回值。例如:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
- 线程池核心参数:
- corePoolSize:线程池的核心线程数。当提交的任务数小于 corePoolSize 时,线程池会创建新的线程来执行任务。
- maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于 corePoolSize 且任务队列已满时,会创建新的线程来执行任务,但线程数不能超过 maximumPoolSize。
- keepAliveTime:线程池中非核心线程的存活时间。当线程空闲时间超过 keepAliveTime 时,非核心线程会被销毁。
- unit:keepAliveTime 的时间单位。
- workQueue:任务队列,用于存放提交的任务。当提交的任务数大于 corePoolSize 时,任务会被放入任务队列中。
- threadFactory:线程工厂,用于创建线程。可以自定义线程的名称、优先级等。
- handler:拒绝策略,当线程池的线程数达到 maximumPoolSize 且任务队列已满时,会调用拒绝策略来处理新提交的任务。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(丢弃新提交的任务)、DiscardOldestPolicy(丢弃任务队列中最旧的任务)。
- HashMap 在多线程环境下的问题:
- 数据丢失:在扩容时可能会导致数据丢失。当 HashMap 进行扩容时,需要重新计算元素的存储位置,如果在多线程环境下同时进行扩容操作,可能会导致部分元素没有被正确复制到新的数组中,从而造成数据丢失。
- 死循环:在扩容时可能会形成死循环。扩容时会创建一个新的更大的数组,然后将原数组中的元素重新计算位置后复制到新数组中。在多线程环境下,可能会出现链表形成环形结构的情况,导致后续在获取元素时进入死循环。
- Spring 框架中 IOC 和 AOP 的原理:
- IOC(控制反转):将对象的创建和依赖注入交给 Spring 容器来管理。通过 XML 配置文件或注解等方式定义对象之间的依赖关系,Spring 容器根据配置创建对象并注入依赖。例如,一个 Service 类依赖于一个 Dao 类,在 Spring 配置中可以指定如何创建 Dao 类并将其注入到 Service 类中。
- AOP(面向切面编程):通过动态代理实现增强功能。在不修改原有业务逻辑的基础上,将一些通用的功能(如日志记录、事务管理等)封装成切面,织入到目标对象的方法执行过程中。Spring 主要通过 JDK 动态代理和 CGLIB 动态代理来实现 AOP。对于实现了接口的类,Spring 使用 JDK 动态代理;对于没有实现接口的类,Spring 使用 CGLIB 动态代理。
- Spring Boot 的优点:
- 配置简单:采用自动配置原理,大大减少了传统 Spring 项目中繁琐的 XML 配置。很多常用的配置都可以通过默认值和少量的属性配置来完成。
- 快速构建项目:提供了多种快速创建项目的方式,如使用 Spring Initializr 可以快速生成包含各种依赖的项目骨架,节省开发时间。
- 内置 Tomcat:内置了 Tomcat 服务器,无需额外手动配置和部署 Tomcat,方便项目的运行和调试。
- MyBatis 核心配置文件主要配置内容:
- 数据源:配置数据库连接相关信息,如数据库驱动、URL、用户名、密码等。
- 事务管理器:指定事务管理的方式,如 JDBC 事务管理器。
- 映射器:配置 SQL 映射文件的位置和命名空间等信息。通过映射器可以将 SQL 语句与 Java 方法进行关联,实现数据的持久化操作。例如,在映射文件中定义 SQL 语句来实现对数据库表的增删改查操作,然后在对应的 Java 接口中定义方法,通过 MyBatis 的动态代理机制来执行这些 SQL 语句。