面试官:第一轮提问开始,首先问你,说说Java中的多线程有哪些实现方式?
王铁牛:这个简单,有继承Thread类、实现Runnable接口,还有实现Callable接口这几种方式。
面试官:不错,回答正确。那再问你,线程池的核心参数有哪些,分别有什么作用?
王铁牛:核心参数有corePoolSize、maximumPoolSize、keepAliveTime、unit和workQueue。corePoolSize是核心线程数,当提交的任务数小于它时,会创建新线程执行任务;maximumPoolSize是最大线程数,当任务数超过corePoolSize且workQueue满时,会创建新线程直到达到这个数;keepAliveTime是线程在超过corePoolSize后,多余线程的存活时间;unit是keepAliveTime的时间单位;workQueue是任务队列,用来存放提交的任务。
面试官:回答得很清晰。最后一个问题,简述一下多线程中的线程安全问题以及如何解决?
王铁牛:线程安全问题就是多个线程同时访问共享资源时可能出现的数据不一致等问题。解决方法有使用synchronized关键字、Lock接口,还有使用ThreadLocal等。
面试官:好,第一轮提问结束,表现不错。接下来第二轮提问。说说JVM的内存模型都有哪些部分?
王铁牛:有程序计数器、虚拟机栈、本地方法栈、堆和方法区。
面试官:嗯。那再问你,简述一下垃圾回收算法有哪些?
王铁牛:有标记清除算法、标记整理算法、复制算法、分代收集算法。
面试官:最后,如何查看JVM的内存使用情况?
王铁牛:可以通过jconsole、jvisualvm等工具查看。
面试官:第二轮提问完毕。现在进入第三轮提问。说说HashMap的底层数据结构和工作原理。
王铁牛:底层是数组+链表+红黑树。当put元素时,先计算hash值,然后找到对应的桶位置,如果桶为空就直接插入,如果不为空就遍历链表或红黑树找到相同key的就更新value,否则就插入新节点,当链表长度大于8且数组长度大于64时,链表会转换为红黑树。
面试官:那ArrayList的底层数据结构是什么,它是线程安全的吗?
王铁牛:底层是数组。它不是线程安全的。
面试官:最后一个问题,简述一下Spring框架的核心特性。
王铁牛:有依赖注入、面向切面编程、IoC容器等。
面试官:好,三轮提问都结束了。你回家等通知吧。
答案:
- Java多线程实现方式:
- 继承Thread类:创建一个类继承Thread类,重写run方法,然后创建该类的实例并调用start方法启动线程。
- 实现Runnable接口:创建一个类实现Runnable接口,实现run方法,然后将该类实例作为参数传递给Thread类的构造函数创建线程对象并启动。
- 实现Callable接口:创建一个类实现Callable接口,实现call方法,该方法有返回值。通过FutureTask类包装Callable对象,然后将FutureTask对象作为参数传递给Thread类构造函数创建线程并启动,可通过FutureTask获取线程执行结果。
- 线程池核心参数及作用:
- corePoolSize:核心线程数。当提交的任务数小于它时,线程池会创建新线程来执行任务。
- maximumPoolSize:最大线程数。当任务数超过corePoolSize且任务队列workQueue已满时,线程池会创建新线程直到线程数达到这个值。
- keepAliveTime:线程在超过corePoolSize后,多余线程的存活时间。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列,用来存放提交的任务。当提交任务数小于corePoolSize时,任务会直接交给核心线程执行;当大于corePoolSize时,任务会被放入任务队列。
- 多线程线程安全问题及解决方法:
- 问题:多个线程同时访问共享资源时,可能导致数据不一致、脏读等问题。
- 解决方法:
- synchronized关键字:可以修饰方法或代码块,保证同一时刻只有一个线程能访问被修饰的资源。
- Lock接口:如ReentrantLock,提供了比synchronized更灵活的锁控制,可实现公平锁等。
- ThreadLocal:为每个使用该变量的线程都提供一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
- JVM内存模型部分:
- 程序计数器:记录当前线程执行的字节码指令地址。
- 虚拟机栈:每个线程都有自己独立的虚拟机栈,用于存储局部变量、操作数栈等。
- 本地方法栈:与虚拟机栈类似,用于执行本地方法。
- 堆:是JVM中最大的内存区域,用于存放对象实例。
- 方法区:存储已被虚拟机加载的类信息、常量、静态变量等。
- 垃圾回收算法:
- 标记清除算法:先标记出所有需要回收的对象,然后统一回收所有被标记的对象。会产生内存碎片。
- 标记整理算法:先标记出存活对象,然后将存活对象向一端移动,最后清理边界以外的内存。
- 复制算法:将内存分为两块,每次只使用其中一块,当这块内存满了,就将存活对象复制到另一块,然后清理原来的那块。适用于新生代。
- 分代收集算法:根据对象的存活周期将内存划分为不同的区域,对不同区域采用不同的垃圾回收算法。一般新生代用复制算法,老年代用标记清除或标记整理算法。
- 查看JVM内存使用情况:
- jconsole:JDK自带的可视化工具,可连接到正在运行的Java程序,实时监控内存、线程等信息。
- jvisualvm:功能更强大的可视化工具,除了基本的监控功能,还能进行性能分析、线程dump等操作。
- HashMap底层数据结构和工作原理:
- 底层数据结构:由数组+链表+红黑树组成。
- 工作原理:当调用put方法时,首先计算key的hash值,然后通过hash值找到对应的数组桶位置。如果桶为空,直接插入新节点;如果桶不为空,遍历链表或红黑树,若找到相同key的节点,则更新其value,若未找到则插入新节点。当链表长度大于8且数组长度大于64时,链表会转换为红黑树,以提高查询效率。
- ArrayList底层数据结构:
- 底层是数组。它通过数组来存储元素,支持随机访问,访问元素的时间复杂度为O(1)。但在进行插入和删除操作时,效率较低,因为可能需要移动大量元素,时间复杂度为O(n)。
- Spring框架核心特性:
- 依赖注入:通过IoC容器将对象之间的依赖关系进行管理和注入,降低对象之间的耦合度。
- 面向切面编程:允许将一些通用功能(如日志、事务管理等)封装成切面,在不修改原有业务代码的情况下,动态地织入到目标对象的方法执行过程中。
- IoC容器:负责创建、配置和管理对象,以及维护对象之间的依赖关系。开发人员只需关注业务逻辑,对象的创建和管理由IoC容器完成。