第一次面试经验总结

第一点:自我介绍:(在第一次自我介绍中我没能体现出我的专业技能和个人亮点,但总体还算流畅)

自我介绍应包括以下几方面

1、基本信息开场

您好!我叫 [fyt],来自于 [学校名称] 22级软件工程专业,

2、专业技能介绍:

我的主攻语言是Java,系统学习了数据结构,熟悉Spring家族,MySQL数据库和redis等主流框架及中间件

3、项目经验:

我在学校担任了两个项目工作室的开发成员,参与过多个合作项目,拥有一定的合作开发经验,主导过2个项目的后端开发。

4、结束语:

以上就是我的基本情况,感谢您的时间。

第二点:项目介绍:(需要优化,项目介绍不够流畅易懂,可以使用简化版star法则)

1、简单介绍项目背景:

我最近参与开发了一个中药知识普及平台的app,旨在将中国千年的中医药知识以现代化、互动化的方式呈现给公众。

2、我的角色与职责:

在这个项目中,我主导了数据库设计和项目后端开发,设计了包括中药库、医院医师库和订单库为主的数据库模型,并整合redis缓存优化了高并发场景下的查询效率。

3、技术难点与解决方案

项目中最具挑战性的是中医药库的搜索性能问题,初期只采用了数据库的直接模糊查询,发现在高并发条件下搜索效率低下,后续我通过设置数据库索引,并使用redis缓存的方式,存入热点数据,减少数据库压力,并通过RabbitMQ消息队列,保证数据库与redis的数据最终一致。

第三点:技术总结:

1、redis

1、在你的项目中redis主要用来存储什么数据?(我认为主要是想问我为什么要用redis)

答:redis主要用来存储经常读取而不经常改变的热点数据,避免反复请求数据库造成效率低下。

2、如何判断一个数据是热点数据呢?(应该是想考我redis的key值删除策略)

答:在我们项目中,会将经常被访问的数据设定为热点数据,比如中医药的和医院医师的大量查询过程中的数据,当这样一个经常被查询的数据被访问时,我们就会将他存入到redis中,采用了惰性淘汰和定期淘汰的策略进行删除,当一个数据一段时间不被访问,或者其有效期过期的时候就会被删除redis缓存。

  1. 惰性删除策略:当一个键值对过期的时候,只有再次用到这个键值对的时候才去检查删除这个键值对,也就是如果用不着,这个键值对就会一直存在。
  2. 定期删除:通过使用定时器,可以保证过期 key 可以被尽快的删除,并且释放过期 key 所占用的内存
3、redis产生的脏数据对生产队列的影响
  • Redis脏数据:Redis内存中数据的值和当前数据库中不一致。
4、说说redis的几种使用场景
  1. 缓存:存热点数据
  2. 分布式锁:使用redisson来实现分布式锁,具体参考:java分布式锁终极解决方案之 redisson_java redisson-优快云博客
  3. 全局id:Redis作为一个高性能的键值存储系统,可以通过INCR命令来生成唯一的ID。这种方法适用于需要高并发生成ID的场景。
  4. 消息队列:Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过lpush将消息放入 list,消费者便可以通过rpop取出该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择sorted set。而pub/sub功能也可以用作发布者 / 订阅者模型的消息。无论使用何种方式,由于 Redis 拥有持久化功能,也不需要担心由于服务器故障导致消息丢失的情况。
  5. 用户登录时用来存用户信息和token
4、全局id的自增原理:

一般来说,使用 Redis 生成全局唯一 ID 会借助 Redis 的原子性自增操作。整体思路是在 Redis 中设置一个键,每次需要生成新的 ID 时,对这个键进行自增操作,得到的结果就是一个唯一的数值,再结合业务相关的信息(如时间戳、机器标识等)组合成最终的全局唯一 ID。

以下是一种简单的实现步骤:

  1. 初始化键值:在系统启动时,在 Redis 中创建一个初始值为 0 的键,例如 global_id_counter
  2. 自增操作:当业务系统需要生成全局唯一 ID 时,调用 Redis 的自增命令对该键进行自增操作。自增操作会返回一个新的数值,这个数值在当前 Redis 实例中是唯一的。
  3. 组合 ID:将自增得到的数值与其他信息(如时间戳、业务标识等)进行组合,形成最终的全局唯一 ID。

2、mysql

1、事务级别分为哪几种级别(回答这里的时候因为太久没有复习过数据库,没回答上)
隔离级别脏读(Dirty Read)不可重复读(Non-Repeatable Read)幻读(Phantom Read)性能
读未提交可能可能可能最高
读已提交不可能可能可能较高
可重复读不可能不可能可能中等
串行化不可能不可能不可能最低
2、关于他的事务有很多种情况,比如幻读,脏读,解释一下。
1. 脏读(Dirty Read)
  • 定义:一个事务读取了另一个事务未提交的数据。

  • 原因:事务隔离级别为读未提交时,允许读取未提交的数据。

  • 问题:如果未提交的事务回滚,读取到的数据就是无效的,导致数据不一致。

2. 不可重复读(Non-Repeatable Read)
  • 定义:在同一事务中,多次读取同一数据,结果不一致。

  • 原因:事务隔离级别为读已提交时,允许其他事务提交修改。

  • 问题:事务内多次读取同一数据时,可能因其他事务的提交而导致结果变化。

3. 幻读(Phantom Read)
  • 定义:在同一事务中,多次查询同一范围的数据,结果集不一致(新增或删除数据)。

  • 原因:事务隔离级别为可重复读时,允许其他事务插入或删除数据。

  • 问题:事务内多次查询同一范围的数据时,可能因其他事务的插入或删除而导致结果集变化。

总结
  • 脏读:读取未提交的数据,可能导致数据无效。

  • 不可重复读:同一事务中多次读取同一数据,结果不一致。

  • 幻读:同一事务中多次查询同一范围的数据,结果集不一致。

3、什么是聚簇索引和非聚簇索引,他们最根本的区别是什么。什么样的索引是一种聚簇索引呢?
1. 聚簇索引(Clustered Index)
  • 定义:聚簇索引决定了表中数据的物理存储顺序。表中的数据按照聚簇索引的键值进行排序和存储。

  • 特点

    • 一个表只能有一个聚簇索引(因为数据只能按一种方式物理排序)。

    • 聚簇索引的叶子节点直接存储数据行(即索引和数据存储在一起)。

    • 对范围查询(如BETWEEN><)性能较好,因为数据是连续存储的。

  • 示例

    • 在MySQL中,主键(Primary Key)默认是聚簇索引。

    • 如果没有主键,InnoDB会选择一个唯一的非空索引作为聚簇索引;如果也没有,则会隐式创建一个隐藏的聚簇索引。

2. 非聚簇索引(Non-Clustered Index)
  • 定义:非聚簇索引是一种独立于数据存储结构的索引,它的叶子节点存储的是指向数据行的指针(如主键值或行ID)。

  • 特点

    • 一个表可以有多个非聚簇索引。

    • 非聚簇索引的叶子节点不存储数据行,而是存储指向数据行的指针。

    • 查询时需要先通过非聚簇索引找到指针,再通过指针查找数据行(可能涉及回表操作)。

  • 示例

    • 在MySQL中,普通索引(如INDEXUNIQUE INDEX)都是非聚簇索引。

    • 例如,对name字段创建的非聚簇索引,叶子节点存储的是name值和对应的主键值。

3. 聚簇索引和非聚簇索引的根本区别
特性聚簇索引非聚簇索引
数据存储方式数据按索引键值物理排序存储数据独立存储,索引存储指向数据的指针
索引数量一个表只能有一个聚簇索引一个表可以有多个非聚簇索引
叶子节点内容存储数据行存储指向数据行的指针
查询性能范围查询性能好单点查询性能好,范围查询可能回表
更新代价数据插入或更新可能导致数据重排序更新代价较低,只需更新索引
4、索引除了b+树以外还有其他情况的索引结构吗?

答:除了b+树以外还存在hash结构和b树等结构。

5、b+树和hash对比,hash的最大的缺点是什么?

答:不支持范围查询是Hash索引的最大缺点。由于哈希函数将键值映射到不同的桶中,桶之间没有顺序关系,因此无法高效地支持范围查询(如WHERE age BETWEEN 20 AND 30)或排序操作(如ORDER BY age)。

6、b+树的数据是如何排放的?

答:b+树的内部节点只存放索引,不存放数据,其数据默认是升序排放的,方便二分查找,所有叶子节点通过指针连接为一个双向链表,支持高效的范围查询

7、b+树为什么会是一种矮胖结构呢?为什么它不会过深?

答:因为B+树是一种多路平衡树,每个节点可以有多个子节点(可以有几百到几千个,当时没答到)

3、JVM

1、JVM分为哪些区?(我主要了解了堆和栈,其他的还不太了解,所以没答出来)

分区线程私有/共享作用异常类型
程序计数器私有记录字节码指令地址
Java虚拟机栈私有存储方法调用的栈帧StackOverflowError, OutOfMemoryError
本地方法栈私有支持Native方法调用StackOverflowError, OutOfMemoryError
Java堆共享存储对象实例和数组OutOfMemoryError
方法区共享存储类信息、常量、静态变量等OutOfMemoryError
运行时常量池共享存储常量池表OutOfMemoryError
直接内存共享堆外内存,支持NIO操作OutOfMemoryError

2、其他JVM问题(主要是垃圾回收机制系列,比较熟悉领域,回答还算完整):

1、垃圾的生命周期

答:new对象进入年轻区中的eden区,经过初次gc后存活的对象进入幸存者区,每次young gc后其存活对象年龄+1,当年龄达到阈值后(默认为15)晋升老年区,当幸存者区内存不足时会触发提前晋升。最后老年区内存不足时full gc,对象被清除,生命结束。

参考资料:

  1. 首先,任何新对象都分配到 eden 空间。两个幸存者空间开始时都是空的。
  2. 当 eden 空间填满时,将触发一个Minor GC(年轻代的垃圾回收,也称为Young GC),删除所有未引用的对象,大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年代。
  3. 所有被引用的对象作为存活对象,将移动到第一个幸存者空间S0,并标记年龄为1,即经历过一次Minor GC。之后每经过一次Minor GC,年龄+1。GC分代年龄存储在对象头的Mark Word里。
  4. 当 eden 空间再次被填满时,会执行第二次Minor GC,将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1并年龄加1,此时S0变为空。
  5. 如此反复在S0和S1之间切换几次之后,还存活的年龄等于15的对象(JDK8默认15,JDK9默认7,-XX:InitialTenuringThreshold=7)在下一次Minor GC时将放到老年代中。 
  6. 如果老年代内存不足够存储新对象,则会执行Full GC(清空整个新生代和老年代)。
  7. 当老年代满了时会触发Full GC或者Major GC(老年代的垃圾回收,清理整个老年代空间)。具体是Full GC还是Major GC取决于用哪个垃圾回收器,传统垃圾回收器会Full GC,G1(优先使用Mixed GC,清空部分新生代和老年代)、ZGC(直接抛出内存溢出错误)等现代回收器会避免Full GC。
2、如何被判断一个对象是否会被淘汰

答:如果一个或多个对象没有任何引用指向它了,那么这个对象现在就是垃圾,如果被定位为了垃圾,就会被垃圾回收机制淘汰。

参考资料:

  • 可达性分析算法:从GC Roots出发,检查对象是否在引用链上。

    • GC Roots包括

      • 栈中局部变量、方法参数。

      • 方法区中类静态属性、常量。

      • JNI引用的对象。

    • 不可达的对象被标记为可回收。

  • 引用类型影响

    • 强引用:不会被回收。

    • 软引用:内存不足时回收。

    • 弱引用:GC时直接回收。

    • 虚引用:无法通过虚引用访问对象,仅用于跟踪回收状态。

3、回收的方法,如标记清除,复制清除,整理清除

算法

步骤

优点

缺点

应用场景

标记-清除

1. 标记存活对象
2. 清除未标记对象

简单

内存碎片、效率低

CMS老年代回收

复制

1. 将存活对象复制到另一块内存
2. 清空原内存

无碎片、高效

内存利用率低(需双倍空间)

新生代(Survivor区)

标记-整理

1. 标记存活对象
2. 存活对象向一端移动
3. 清理边界外内存

无碎片、内存利用率高

移动对象开销大

老年代(Serial Old, G1)

4、g1,cms的垃圾回收机制

 G1(Garbage-First,垃圾优先收集器):

  • 介绍:以延迟可控并保证高吞吐量为目标,为了适应内存大小和处理器数量不断扩大而在JDK7推出的垃圾回收器。开创了收集器面向局部收集的设计思路和基于Region(区域)的内存布局形式。JDK8支持并发类卸载后被Oracle官方称为“全功能的垃圾收集器”。并行低停顿,除了并发标记外需要stw,但耗时很短(初始标记和最终标记是真短,筛选回收是有指定STW)。
  • 实现机制:不再把堆划分为连续的分代,而是将堆内存分割成2048个大小相等的Region,各Region根据需要扮演伊甸园区、幸存区、老年代区、巨大区。垃圾优先收集器跟踪各Region里垃圾的回收价值(回收空间大小和预计回收时长),在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,回收优先级最高的那些Region,以达到垃圾优先的效果。
  • 设置最大停顿时间:-XX:MaxGCPauseMillis=默认0.2s
  • Humongous Region(巨大区):存储大小超过Region一半空间的大对象,如果大对象的内存大小超过了Region大小,将会被存在几个连续的巨大区里。G1的大多数行为把巨大区看作老年代的一部分。
  • 算法:分区收集算法(整体是标记整理算法、Region之间标记复制算法)
  • 回收区域:整堆。整堆里哪个Region垃圾最多,回收收益最大。
  • 步骤:
    • 初始标记:标记GC Roots直接关联的可达对象。单线程且停顿用户线程,速度很快。
    • 并发标记:从直接关联对象并发遍历整个图,标记可达对象。并发不停顿。
    • 最终标记:重新标记所有存活的对象。并发停顿。采用SATB算法,效率比CMS重新标记高。并发停顿。
    • 筛选回收:根据优先级列表,回收价值高的一些Region,将存活对象通过标记复制算法复制到同类型的空闲Region。根据指定的最大停顿时间回收,因此可能来不及回收所有垃圾对象,但能保证回收到最高回收价值的垃圾。并发停顿。

CMS(并发标记清除收集器):

  • 介绍:以最短停顿时间为目标,JDK1.5推出,第一次实现了垃圾收集线程和用户线程同时工作。多线程并行回收老生代,低stw。初始标记和重新标记需要stw,但耗时很短。
  • 算法:标记清除算法。不使用标记整理算法是为了保证清除时不影响用户线程中的工作线程,如果使用标记整理算法的话工作线程引用指向的对象地址就都变了。
  • 回收区域:老年代
  • 步骤:
    • 初始标记:标记GC Roots直接关联的对象。单线程且停顿用户线程,速度很快。
    • 并发标记:从直接关联对象并发遍历整个图,标记可达对象。并发不停顿。
    • 重新标记:修正上一步用户线程变动的标记。并发停顿。速度远比并发标记阶段快。注意只能修正原有对象不能修正新增对象,即只能修正原有对象非可达变可达、可达变非可达。
    • 并发清除:并发线性遍历并清理未被标记的对象。并发不停顿。
  • 优点:
    • 并发速度快;
    • 低停顿:用户线程和垃圾回收器同时执行,仅初始标记和重新标记阶段需要停顿,这两个阶段运行速度很快。
  • 缺点:
    • 并发占线程拖慢速度
    • 有内存碎片:内存不规整,需要维护空闲列表。
    • 无法处理浮动垃圾:并发标记阶段会产生新对象,重新标记阶段又只能修正不能新增,所以会出现浮动垃圾。
    • 回收时要确保用户线程有足够内存:不能等老年代满了再回收,而是内存到达某个阈值后回收,防止用户线程在并发执行过程中新创建对象导致内存不够,导致虚拟机补偿使用Serial Old收集器进行回收并处理内存碎片,从而浪费更多时间。CMS默认是老年代68%时触发回收机制。-XX:CMSInitiatingOccupancyFraction
  • 应用场景:因为底层是标记清除算法,所以有内存碎片,适合小应用。
5、g1如何判断什么时候应该垃圾回收的呢?(回答有争议,后续互联网再复习一下)

答:g1有两种垃圾回收策略,一种是时间gc,根据设置的默认回收时间,动态调整回收区域。一种是内存gc,当堆内存占用超过阈值时,启动并发标记周期。

  1. 预测模型:根据-XX:MaxGCPauseMillis(默认200ms)动态调整回收区域。

  2. 阈值触发:当堆内存占用超过阈值(默认45%,-XX:InitiatingHeapOccupancyPercent)时,启动并发标记周期。

6、有没有什么其他情况,对象会提前进入老年区?老年担保策略去了解一下

答:有的,我目前了解到的对象进入老年区有三种情况:

  • 大对象直接进入老年区
  • 长期存活对象(默认年龄达到15次时)进入老年区
  • 动态年龄判断:Survivor区中相同年龄对象的总大小超过Survivor空间一半,该年龄及以上对象直接晋升。

7、young GC和full GC的区别

答:young GC的回收区域在新生代(Eden + survivor),在Eden区满时触发,将Eden区和survivor区中的存活对象向另一处survivor区复制清除;而full GC的回收区域是整个堆,即新生代 + 老年代 + 元空间,其触发条件在老年代空间不足时,元空间不足或者调用了system.gc()时触发,会有很长的卡顿时间,应尽量避免。

参考资料

  • 调用System.gc():调用 System.gc() (用于请求 JVM 执行垃圾回收)时,JVM 会建议执行 Full GC(具体是否执行取决于垃圾回收器实现的System.gc()逻辑,例如Parallel GC、CMS等传统垃圾回收器该方法会Full GC,而G1、ZGC等现代回收器不会Full GC)。
  • 空间分配担保失败:当对象从新生代晋升到老年代前,要检查老年代是否有足够空间容纳新对象,这叫做空间分配担保。如果空间不足,则空间分配担保失败,JVM会执行Full GC。
  • 老年代满了:当老年代满了时会触发Full GC或者Major GC(老年代的垃圾回收,清理整个老年代空间)。具体是Full GC还是Major GC取决于用哪个垃圾回收器,传统垃圾回收器会Full GC,G1(优先使用Mixed GC,清空部分新生代和老年代)、ZGC(直接抛出内存溢出错误)等现代回收器会避免Full GC。
  • 方法区满了:当方法区(Method Area)空间不足时,会触发Full GC。
8、在什么情况下选择哪种gc方式?比如在什么业务场景下使用g1回收?为什么?

答:我们在在高并发且堆内存较大的场景下(如实时在线服务),可以选择G1回收器。G1通过动态分区(Region)和可预测的停顿时间控制(如设置-XX:MaxGCPauseMillis=200ms),能够在高并发下减少GC导致的业务停顿,同时高效管理大内存堆,避免内存碎片问题。相较于CMS,G1更适合现代应用的低延迟需求。

参考资料:

何时选择G1?
  1. 业务场景

    • 大堆内存(>4GB):G1通过分区(Region)管理内存,适合处理大规模堆。

    • 低延迟需求:如实时交易系统、在线服务,要求GC停顿时间可控(例如-XX:MaxGCPauseMillis=200ms)。

    • 混合负载:需同时处理新生代和老年代垃圾,避免频繁Full GC。

  2. 核心优势

    • 可预测的停顿时间:通过MaxGCPauseMillis参数设定目标停顿时间,动态调整回收区域。

    • 高效内存整理:通过复制算法整理Region,减少内存碎片。

    • 并发标记:大部分标记阶段与应用线程并发执行,减少STW时间。

  3. 为什么选G1而非其他GC?

    • 对比CMS:CMS已废弃,且存在内存碎片问题,而G1通过整理Region避免碎片。

    • 对比Parallel GC:Parallel GC以吞吐量为优先,但停顿时间不可控,不适合低延迟场景。

    • 对比ZGC/Shenandoah:ZGC/Shenandoah停顿时间更低(<10ms),但需要JDK 11+,且对超大堆(如数十GB)优化更好。

  • 业务场景选择

    • G1:适用于大堆(>4GB)、低延迟要求的场景(如实时交易系统)。

    • CMS:已不推荐使用(JDK 9后标记为废弃),仅用于旧版中小堆且需低停顿的场景。

    • Parallel GC:吞吐量优先(如后台批处理系统),通过-XX:+UseParallelGC启用。

    • ZGC/Shenandoah:超低延迟(<10ms),适合超大堆(如JDK 11+的云原生应用)。

9、是否是所有的对象都会存在于堆区呢?

答:否:通过逃逸分析,JVM可能将未逃逸的对象分配在栈上(栈上分配),或直接拆解为标量(标量替换),避免堆内存分配。

参考资料:

在ObjectEscape类中,存在一个成员变量user,我们在init()方法中,创建了一个User类的对象,并将其赋值给成员变量user。此时,对象被复制给了成员变量,可能被外部使用,此时的变量就发生了逃逸。

另一种典型的场景就是:对象通过return语句返回。如果对象通过return语句返回了,此时的程序并不能确定这个对象后续会不会被使用,外部的线程可以访问到这个变量,此时对象也发生了逃逸。

我们可以用下面的代码来表示这个现象。

/**

  • @author binghe

  • @description 对象逃逸示例2

*/

public class ObjectReturn{

public User createUser(){

User user = new User();

return user;

}

}

10、对象的逃逸有了解吗?

答:Java中的对象不一定是在堆上分配的,因为JVM通过逃逸分析,能够分析出一个新对象的使用范围,并以此确定是否要将这个对象分配到堆上。

4、Java基础

1、hashmap的数据结构与put过程以及扩容过程(较为了解)

hashmap的数据结构:HashMap 的底层是基于数组加链表的数据结构📊。每个数组的槽位(bucket)可以存储多个键值对,这些键值对通过链表连接起来。当多个键的哈希值相同(即哈希冲突发生时),这些键值对会形成一个链表🔗。

put过程:

  1. 计算哈希值:首先,调用键对象的 hashCode() 方法得到其哈希码,然后通过 HashMap 内部的哈希函数对哈希码进行进一步处理,以减少哈希冲突的可能性。这个哈希函数会将哈希码与 HashMap 的容量进行一些位运算,得到一个在当前容量范围内的索引值。
  2. 检查数组是否为空或长度为 0:如果 HashMap 内部的数组(也称为哈希桶数组)为空或者长度为 0,会触发一次扩容操作,将数组初始化为默认大小(通常是 16)。
  3. 确定桶位置:根据计算得到的索引值,找到对应的哈希桶。如果该桶为空,直接创建一个新的节点(存储键值对)放入该桶中。
  4. 处理哈希冲突:如果该桶已经有节点存在,说明发生了哈希冲突。此时会遍历该桶中的链表或红黑树(当链表长度达到一定阈值,默认是 8,且数组长度达到 64 时,链表会转换为红黑树)。
    • 链表情况:遍历链表,比较每个节点的键是否与要插入的键相等(通过 equals() 方法)。如果找到相等的键,则更新该节点的值;如果遍历完链表都没有找到相等的键,则在链表尾部插入一个新节点。
    • 红黑树情况:在红黑树中查找是否存在相等的键。如果存在,更新其值;如果不存在,插入一个新节点,并可能会对红黑树进行平衡调整。

扩容机制

当 HashMap 中的元素数量超过阈值(容量乘以负载因子)时,会触发扩容操作。扩容的主要步骤如下:

  1. 创建新数组:将 HashMap 内部的数组容量扩大为原来的 2 倍,并创建一个新的数组。
  2. 重新哈希:遍历原数组中的每个桶,将其中的节点重新计算哈希值,并根据新的容量确定在新数组中的位置,然后将节点插入到新数组的相应桶中。
    • 链表节点:对于链表节点,会将链表拆分为两个链表,一个链表的节点在新数组中的位置与原位置相同,另一个链表的节点在新数组中的位置是原位置加上原数组的长度。
    • 红黑树节点:对于红黑树节点,同样会进行拆分操作。如果拆分后红黑树的节点数量小于等于 6,会将红黑树转换回链表。
  3. 更新引用:将 HashMap 的引用指向新数组,完成扩容。

2、int的拆装箱(这方面具体底层原理了解不多,先说说我知道的,面试时这里脑袋卡机了,不知道面试官想问什么,就回答的不太清楚)

自动装箱和拆箱

(1)装箱:把基本数据类型转换为对应的包装类类型;

(2)拆箱:把包装类类型转换为对应的基本数据类型。

3、String、StringBuffer和StringBuilder的区别(面试没有回答上,目前还不太了解)

String、StringBuffer和StringBuilder的区别(面试题)_android 开发 string stringbuffer stringbuilder面试题-优快云博客

5、多线程及其他问题

1、线程池的主要参数有什么?

        线程池的七大参数

  • corePoolSize: 核心线程数,创建不能被回收,可以设置被回收。
  • maximumPoolSize: 最大线程数。
  • keepAliveTime: 空闲线程存活时间 / 非核心线程的空闲存活时间(超时后回收)。
  • unit: 存活时间的单位(如秒、毫秒)
  • workQueue: 等待队列 / 任务队列:用于保存等待执行的任务(如ArrayBlockingQueue)。
  • threadFactory: 线程工厂:用于创建新线程(可自定义线程名、优先级等)。
  • handler: 拒绝策略:当任务数超过队列容量和最大线程数时的处理策略(如抛出异常)。

2、线程如何实现多个线程切换执行多个任务的呢?

  • 操作系统调度:线程切换由操作系统内核的线程调度器完成,基于时间片轮转或优先级抢占。

  • 上下文切换:CPU保存当前线程的上下文(寄存器、程序计数器等),加载下一个线程的上下文。

  • Java线程模型:Java线程是操作系统原生线程的映射,通过Thread类和Runnable/Callable接口实现多任务调度。

3、java实现同步的锁有哪些?

锁类型特点
synchronized关键字,基于JVM内置锁,支持代码块或方法同步,自动释放锁。
ReentrantLock显式锁,提供更灵活的操作(如可中断、超时获取锁),需手动释放。
ReadWriteLock读写分离锁(如ReentrantReadWriteLock),读操作共享,写操作互斥。
StampedLock乐观读锁,通过“邮戳”机制减少读写冲突(JDK 8+)。
LockSupport底层工具类,用于挂起或唤醒线程(如park()unpark())。

4、锁的升级。

答:synchronized锁在竞争加剧时会经历以下升级过程:

  1. 无锁:初始状态,无线程竞争。

  2. 偏向锁:单个线程多次访问时,通过CAS标记线程ID,减少同步开销。

  3. 轻量级锁(自旋锁):多个线程轻度竞争时,通过自旋尝试获取锁,避免线程阻塞。

  4. 重量级锁:竞争激烈时,升级为基于操作系统互斥量(Mutex)的锁,线程进入阻塞队列。

目的:在无竞争或低竞争时减少开销,高竞争时保证线程安全。

5、悲观锁和乐观锁最大的区别是什么?

答:悲观锁认为每次操作都有可能引发并发冲突,因此先尝试加锁再操作数据,如果加锁失败则进入等待,乐观锁不认为每次操作都可能引发并发冲突,因此先修改数据,再比对数据冲突。

第四点:自我总结:

第一次的面试情况无疑是糟糕的,我在MySQL基础以及多线程操作中的表现相当失败,在Java基础上也需下功夫,代码底层逻辑也不够清晰,但所幸的是我在此次面试过程中找到了我本身的大量问题,希望在之后的面试中能够有所成长与突破,希望我的一些面试经验能够帮助到大家,愿大家都能够找到心仪的工作岗位。

最后推荐一点我在写此面试经验,复盘之前不懂的问题时所参考的一些优秀文章:

java高频面试题(2024最新)_java 高频面试题总结-优快云博客

String、StringBuffer和StringBuilder的区别(面试题)_android 开发 string stringbuffer stringbuilder面试题-优快云博客

【Java面试题汇总】JVM篇(2025版)_jvm面试题-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值