主题
面试速答(先看这里)
**一句话结论:**乐观锁适用于读操作频繁,写操作相对较少的场景。
60秒标准回答:
乐观锁和悲观锁是在处理并发访问时使用的两种不同的策略
乐观锁的基本思想是假设冲突很少发生 ,每个线程在修改数据之前,先获取一个版本号或时间戳,并在更新时检查这个版本号或时间戳,以确保其他线程没有同时修改数据
乐观锁适用于读操作频繁,写操作相对较少的场景。 当冲突较少,且并发写入的概率较低时,乐观锁的性能可能更好
**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点
回答主线:
- **要点1:**乐观锁的基本思想是假设冲突很少发生 ,每个线程在修改数据之前,先获取一个版本号或时间戳,并在更新时检查这个版本号或时间戳,以确保其他线程没有同时修改数据。
- **要点2:**悲观锁适用于写操作较为频繁,且并发写入的概率较高的场景。
- **要点3:**乐观锁和悲观锁还有个区别:乐观锁因为比较乐观,所以一般是先做业务逻辑操作,比如参数处理,内存中进行模型组装调整,然后再去更新数据库。
- **要点4:**而高并发的写操作时,你干了一大堆活,把模型都组装好了,内存计算也都做完了,结果最后去数据库那更新的时候发现版本号变了。
- **要点5:**综上,在高并发场景中,一般来说 并发写入的冲突较为频繁,所以建议优先考虑悲观锁。
**记忆锚点:**乐观锁 → 悲观锁 → 乐观锁和悲观锁 → 应该是先尝试获取锁 → fail-fast → 建议优先考虑悲观锁
关键取舍:
- 乐观锁适用于读操作频繁,写操作相对较少的场景。
- 当冲突较少,且并发写入的概率较低时,乐观锁的性能可能更好。
- 悲观锁则是假设冲突经常发生 ,因此在访问共享资源之前,线程会先获取锁,确保其他线程无法同时访问相同的数据。
易错提醒:
- 悲观锁可以有效地避免多个线程同时修改相同数据的情况。
- 所以,应该是先尝试获取锁,如果获取锁成功,再进行业务操作,否则就直接返回失败。
加分表达:
- 悲观锁适用于写操作较为频繁,且并发写入的概率较高的场景。
追问准备:
- 围绕「乐观锁」:底层原理是什么?使用时有哪些边界和常见坑?
- 围绕「悲观锁」:底层原理是什么?使用时有哪些边界和常见坑?
- 围绕「乐观锁和悲观锁」:底层原理是什么?使用时有哪些边界和常见坑?
- 如果线上出现异常,你会如何定位、验证并规避?
典型回答
乐观锁和悲观锁是在处理并发访问时使用的两种不同的策略。
乐观锁的基本思想是假设冲突很少发生,每个线程在修改数据之前,先获取一个版本号或时间戳,并在更新时检查这个版本号或时间戳,以确保其他线程没有同时修改数据。
**乐观锁适用于读操作频繁,写操作相对较少的场景。**当冲突较少,且并发写入的概率较低时,乐观锁的性能可能更好。
悲观锁则是假设冲突经常发生,因此在访问共享资源之前,线程会先获取锁,确保其他线程无法同时访问相同的数据。这可能导致并发性降低,因为只有一个线程能够访问数据。
**悲观锁适用于写操作较为频繁,且并发写入的概率较高的场景。**悲观锁可以有效地避免多个线程同时修改相同数据的情况。
乐观锁和悲观锁还有个区别:乐观锁因为比较乐观,所以一般是先做业务逻辑操作,比如参数处理,内存中进行模型组装调整,然后再去更新数据库。悲观锁因为比较悲观,所以会先尝试加锁,然后再去做业务逻辑操作。
也就是说,乐观锁是先干活,后加锁。悲观锁是先加锁,再干活。
而高并发的写操作时,你干了一大堆活,把模型都组装好了,内存计算也都做完了,结果最后去数据库那更新的时候发现版本号变了。这不是大冤种吗?
所以,应该是先尝试获取锁,如果获取锁成功,再进行业务操作,否则就直接返回失败。这样可以做fail-fast。
综上,在高并发场景中,一般来说**并发写入的冲突较为频繁,所以建议优先考虑悲观锁。**即在做并发操作前,先尝试获取锁,如果获取锁成功,在进行业务操作,否则就直接返回失败。
比如,我们通常在并发场景下都使用分布式锁,即先加分布式锁,然后再操作。这个就是一个悲观锁的思想,我认为冲突一定很大,所以我先尝试加锁。拿到锁再开始干活。
扩展知识
乐观锁并非无锁
不管是乐观锁还是悲观锁,都并不是无锁的。这一点一定要注意。