挣公司的钱,用公司的电,吃公司的饭。
共 8047字,需浏览 17分钟
·
2024-08-19 14:04
大家好,我是二哥呀。
工作是为了什么,我愿总结为挣公司的钱,用公司的电,吃公司的饭,然后实现自己的人生价值🤣。
牛客上就有这样一位在 oppo 工作的牛友,发帖说自己正在兑换公司的旅游福利,选了东欧 10 天游,5 天带薪。没有领导蹲点催项目进度的日子,真的只有一个字来形容——爽死了。
我们找工作,最大的初衷我想应该是能养活自己(瞧我这没出息的样)。
假如能在养活自己的同时,养活家人,还能有一份相对舒适的工作体感,我想 99.99% 的打工人都会感到满足。但现实往往会站在我们初衷的对立面。
于是我们就需要提升自己的能力(寒窗苦读数十年),去找一个好的公司。
简历和面试,其实就是为了让【我们认为自己的价值】和【公司认为我们的价值】相匹配。而职场之所以“卷”的本质原因,我想可能是因为:
我们总是天真的以为,自己越努力,就会挣的越多,但可能是老板挣的更多(😄)。
那今天我们就以《Java 面试指南》中收录的《oppo 面经同学 1 后端开发秋招一面》为例,来看看绿厂的面试官都喜欢问哪些问题,好做到知彼知己,百战不殆。
能看得出来,面试内容仍然是围绕着二哥一直强调的 Java 后端四大件展开,所以准备面试的小伙伴一定要有的放矢,这样才能事半功倍。(拿到 offer 记得来给二哥报喜哦,我想脸上贴贴金 dog)
1、《30天速通Java.pdf》下载 2、三分恶面渣逆袭在线版:https://javabetter.cn/sidebar/sanfene/nixi.html
oppo 面经同学 1 后端开发秋招一面
讲讲ReentrantLock
ReentrantLock 是可重入的独占锁,只能有一个线程可以获取该锁,其它获取该锁的线程会被阻塞。
可重入表示当前线程获取该锁后再次获取不会被阻塞,也就意味着同一个线程可以多次获得同一个锁而不会发生死锁。
ReentrantLock 的加锁和解锁:
// 创建非公平锁
ReentrantLock lock = new ReentrantLock();
// 获取锁操作
lock.lock();
try {
// 执行代码逻辑
} catch (Exception ex) {
// ...
} finally {
// 解锁操作
lock.unlock();
}
new ReentrantLock()
默认创建的是非公平锁 NonfairSync。在非公平锁模式下,锁可能会授予刚刚请求它的线程,而不考虑等待时间。
ReentrantLock 也支持公平锁,该模式下,锁会授予等待时间最长的线程。
ReentrantLock 内部通过一个计数器来跟踪锁的持有次数。
当线程调用lock()
方法获取锁时,ReentrantLock 会检查当前状态,判断锁是否已经被其他线程持有。如果没有被持有,则当前线程将获得锁;如果锁已被其他线程持有,则当前线程将根据锁的公平性策略,可能会被加入到等待队列中。
线程首次获取锁时,计数器值变为 1;如果同一线程再次获取锁,计数器增加;每释放一次锁,计数器减 1。
当线程调用unlock()
方法时,ReentrantLock 会将持有锁的计数减 1,如果计数到达 0,则释放锁,并唤醒等待队列中的线程来竞争锁。
线程池都有哪些以及核心参数介绍下
可以通过 Executors 工厂类来创建四种常见的线程池:
-
newFixedThreadPool (固定线程数目的线程池) -
newCachedThreadPool (可缓存线程的线程池) -
newSingleThreadExecutor (单线程的线程池) -
newScheduledThreadPool (定时及周期执行的线程池)
线程池主要参数有哪些?
线程池有 7 个参数,需要重点关注corePoolSize
、maximumPoolSize
、workQueue
、handler
这四个。
我一一说一下:
①、corePoolSize
定义了线程池中的核心线程数量。即使这些线程处于空闲状态,它们也不会被回收。这是线程池保持在等待状态下的线程数。
②、maximumPoolSize
线程池允许的最大线程数量。当工作队列满了之后,线程池会创建新线程来处理任务,直到线程数达到这个最大值。
③、workQueue
用于存放待处理任务的阻塞队列。当所有核心线程都忙时,新任务会被放在这个队列里等待执行。
④、handler
拒绝策略 RejectedExecutionHandler,定义了当线程池和工作队列都满了之后对新提交的任务的处理策略。常见的拒绝策略包括抛出异常、直接丢弃、丢弃队列中最老的任务、由提交任务的线程来直接执行任务等。
Mysql索引的数据结构,为什么选择这样的数据结构
MySQL 的默认存储引擎是 InnoDB,它采用的是 B+树索引,B+树是一种自平衡的多路查找树,和红黑树、二叉平衡树不同,B+树的每个节点可以有 m 个子节点,而红黑树和二叉平衡树都只有 2 个。
和 B 树不同,B+树的非叶子节点只存储键值,不存储数据,而叶子节点存储了所有的数据,并且构成了一个有序链表。
这样做的好处是,非叶子节点上由于没有存储数据,就可以存储更多的键值对,再加上叶子节点构成了一个有序链表,范围查询时就可以直接通过叶子节点间的指针顺序访问整个查询范围内的所有记录,而无需对树进行多次遍历。查询的效率会更高。
建索引的时候应该注意什么
尽管索引能提高查询性能,但不当的使用也会带来一系列问题。在加索引时需要注意以下几点:
①、选择合适的列作为索引
-
经常作为查询条件(WHERE 子句)、排序条件(ORDER BY 子句)、分组条件(GROUP BY 子句)的列是建立索引的好候选。 -
区分度低的字段,例如性别,不要建索引 -
频繁更新的字段,不要作为主键或者索引 -
不建议用无序的值(例如身份证、UUID )作为索引,当主键具有不确定性,会造成叶子节点频繁分裂,出现磁盘存储的碎片化
②、避免过多的索引
-
每个索引都需要占用额外的磁盘空间。 -
更新表(INSERT、UPDATE、DELETE 操作)时,所有的索引都需要被更新。 -
维护索引文件需要成本;还会导致页分裂,IO 次数增多。
③、利用前缀索引和索引列的顺序
-
对于字符串类型的列,可以考虑使用前缀索引来减少索引大小。 -
在创建复合索引时,应该根据查询条件将最常用作过滤条件的列放在前面。
选一个项目介绍下,项目中遇到的最大问题是啥,怎么解决的
PmHub 是一套基于 SpringCloud Alibaba & LLM 的智能项目管理系统,目前拆分了用户、流程、项目管理、认证等 4 个微服务。
项目遇到最大的困难是从 Spring Boot 单体版迁移到 Spring Cloud 微服务架构版,遇到了蛮多问题,比如说 Docker 部署的 MySQL 总是莫名其妙挂掉、ServerConfig 的版本冲突问题、微服务启动链接不到 DataSource 数据源等等。
遇到问题的时候,我会先去看源码,尝试自己解决,如果实在没有头绪,我会借助 Google、GPT4o,基本上经过深思熟虑和反复调试后,问题都能够得到解决。
讲讲Mysql的四个隔离级别
事务的隔离级别定了一个事务可能受其他事务影响的程度,MySQL 支持的四种隔离级别分别是:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
什么是读未提交?
读未提交是最低的隔离级别,在这个级别,当前事务可以读取未被其他事务提交的数据,以至于会出现“脏读”、“不可重复读”和“幻读”的问题。
什么是读已提交?
当前事务只能读取已经被其他事务提交的数据,可以避免“脏读”现象。但不可重复读和幻读问题仍然存在。
什么是可重复读?
确保在同一事务中多次读取相同记录的结果是一致的,即使其他事务对这条记录进行了修改,也不会影响到当前事务。
可重复读是 MySQL 默认的隔离级别,避免了“脏读”和“不可重复读”,但可能会出现幻读。
什么是串行化?
最高的隔离级别,通过强制事务串行执行来避免并发问题,可以解决“脏读”、“不可重复读”和“幻读”问题。
但会导致大量的超时和锁竞争问题。
讲讲Mysql的MVCC机制
MVCC 是多版本并发控制(Multi-Version Concurrency Control)的简称,主要用来解决数据库并发问题。
在支持 MVCC 的数据库中,当多个用户同时访问数据时,每个用户都可以看到一个在某一时间点之前的数据库快照,并且能够无阻塞地执行查询和修改操作,而不会相互干扰。
在传统的锁机制中,如果一个事务正在写数据,那么其他事务必须等待写事务完成才能读数据,MVCC 允许读操作访问数据的一个旧版本快照,同时写操作创建一个新的版本,这样读写操作就可以并行进行,不必等待对方完成。
在 MySQL 中,特别是 InnoDB 存储引擎,MVCC 是通过版本链和 ReadView 机制来实现的。
什么是版本链?
在 InnoDB 中,每一行数据都有两个隐藏的列:一个是 DB_TRX_ID,另一个是 DB_ROLL_PTR。
-
DB_TRX_ID
,保存创建这个版本的事务 ID。 -
DB_ROLL_PTR
,指向 undo 日志记录的指针,这个记录包含了该行的前一个版本的信息。通过这个指针,可以访问到该行数据的历史版本。
说说什么是 ReadView?
ReadView(读视图)是 InnoDB 为了实现一致性读(Consistent Read)而创建的数据结构,它用于确定在特定事务中哪些版本的行记录是可见的。
ReadView 主要用来处理隔离级别为"可重复读"(REPEATABLE READ)和"读已提交"(READ COMMITTED)的情况。因为在这两个隔离级别下,事务在读取数据时,需要保证读取到的数据是一致的,即读取到的数据是在事务开始时的一个快照。
raft主节点挂了怎么选从节点
Redis 使用 Raft 算法实现领导者选举的:当主节点挂掉后,新的主节点是由剩余的从节点发起选举后晋升的。
①、每个在线的 Sentinel 节点都有资格成为领导者,当它确认主节点下线时候,会向其他哨兵节点发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。
这个投票过程称为“Leader 选举”。候选者会给自己先投 1 票,然后向其他 Sentinel 节点发送投票的请求。
②、收到请求的 Sentinel 节点会进行判断,如果候选者的日志与自己的日志一样新,任期号也小于自己,且之前没有投票过,就会同意投票,回复 Y。否则回复 N。
③、候选者收到投票后会统计支持自己的得票数,如果候选者获得了集群中超过半数节点的投票支持(即多数原则),它将成为新的主节点。
新的主节点在确立后,会向其他从节点发送心跳信号,告诉它们自己已经成为主节点,并将其他节点的状态重置为从节点。
④、如果多个节点同时成为候选者,并且都有可能获得足够的票数,这种情况下可能会出现选票分裂。也就是没有候选者获得超过半数的选票,那么这次选举就会失败,所有候选者都会再次发起选举。
为了防止无限制的选举失败,每个节点都会有一个选举超时时间,且是随机的。
超时时间指从节点在没有收到主节点的心跳信号或日志追加请求后,等待多长时间才会认为主节点已挂掉,从而进入候选状态并发起选举。
讲一下Spring Bean的生命周期
Spring 中 Bean 的生命周期大致分为四个阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialization)、销毁(Destruction)。
对应的完整步骤如下图所示:
-
实例化:Spring 容器根据 Bean 的定义创建 Bean 的实例,相当于执行构造方法,也就是 new 一个对象。 -
属性赋值:相当于执行 setter 方法为字段赋值。 -
初始化:初始化阶段允许执行自定义的逻辑,比如设置某些必要的属性值、开启资源、执行预加载操作等,以确保 Bean 在使用之前是完全配置好的。 -
销毁:相当于执行 = null
,释放资源。
讲一下Spring事务传播机制
事务的传播机制定义了在方法被另一个事务方法调用时,这个方法的事务行为应该如何。
Spring 提供了一系列事务传播行为,这些传播行为定义了事务的边界和事务上下文如何在方法调用链中传播。
-
REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。Spring 的默认传播行为。 -
SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。 -
MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 -
REQUIRES_NEW:总是启动一个新的事务,如果当前存在事务,则将当前事务挂起。 -
NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则将当前事务挂起。 -
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前事务不存在,则行为与 REQUIRED 一样。嵌套事务是一个子事务,它依赖于父事务。父事务失败时,会回滚子事务所做的所有操作。但子事务异常不一定会导致父事务的回滚。
内容来源
-
星球嘉宾三分恶的面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html -
二哥的 Java 进阶之路(GitHub 已有 12000+star):https://javabetter.cn
ending
一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 6000 多名球友加入了,如果你也需要一个良好的学习环境,戳链接 🔗 加入我们吧。这是一个编程学习指南 + Java 项目实战 + LeetCode 刷题的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。
两个置顶帖「球友必看」和「知识图谱」里已经沉淀了非常多优质的学习资源,相信能帮助你走的更快、更稳、更远。
欢迎点击左下角阅读原文了解二哥的编程星球,这可能是你学习求职路上最有含金量的一次点击。
最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。