朋友们,今天我们来聊聊 Java 面试中一个经常被问到的高频问题——乐观锁和悲观锁!这不仅是社招面试的重点,也是工作中优化并发性能的必备知识。
作为一个经历过 N 场面试、踩过无数坑、在面试官手里九死一生的“战损程序员”,我可以很负责任地告诉你:
面试官喜欢问这个问题!
所以,今天我就用故事+代码+实战的方式,把这个问题讲透!看完这篇文章,你的面试表现必定技高一筹!
故事背景:抢票大战假设你有一个抢票系统,用户在高峰期疯狂点击“抢票”按钮,每张票都炙手可热。
乐观锁的思路是:“我觉得你不会抢走这张票,我先买着,等我支付时再检查票是否被抢光。”
悲观锁的思路是:“谁也别抢,我先锁住这张票,等我支付完再放开。”
我们用更形象的比喻来理解:
那么,两者的具体实现方式又是什么呢?继续往下看!
悲观锁的实现方式1. Synchronized & ReentrantLock
悲观锁的核心思想是独占式访问,即当前线程访问资源时,会阻塞其他线程的访问。最典型的实现方式是Synchronized 和 ReentrantLock。
(1)Synchronized
特点:
JVM 内置锁,使用方便。
适用于简单场景,如同步方法或同步代码块。
(2)ReentrantLock
特点:
显示加锁、解锁,比 Synchronized 更灵活。
可实现公平锁(避免某些线程一直得不到锁)。
2. 数据库悲观锁(for update)
如果你的抢票逻辑是基于数据库的,我们可以使用悲观锁来防止超卖:
特点:
锁住行数据,其他事务无法修改。
适用于高并发场景,但可能导致锁竞争,影响性能。
乐观锁的实现方式1. CAS(Compare and Swap)
CAS 是乐观锁的核心机制,它的思路是先读取数据,更新时检查数据是否被改动,如果没有变化就更新,否则重试。
(1)Java Atomic 类
特点:
无锁机制,高并发下性能更好。
适用于计数、累加等轻量级操作。
2. 数据库乐观锁(版本号机制)
在数据库中,我们可以用版本号来实现乐观锁。
(1)数据库表设计
(2)更新逻辑
特点:
只有当version 没变,才会成功更新,否则说明数据被别人改了,需要重试。
(3)Java 代码实现
总结:
读多写少,优先用乐观锁(减少阻塞)。
读写频繁,考虑悲观锁(避免冲突)。
高并发环境,推荐CAS 或版本号机制。
数据库更新,根据实际情况选择for update(悲观锁)或 version(乐观锁)。
面试官可能的追问点1、CAS 有哪些缺点?
ABA 问题(解决方案:AtomicStampedReference)
自旋失败(高并发下可能导致 CPU 消耗大)
2、如何优化数据库悲观锁?
限制锁定的范围(只锁定必要字段)
使用合适的事务隔离级别(避免死锁)
3、在分布式环境下如何实现乐观锁?
Redis 分布式锁(如 Redisson)
Zookeeper 临时节点
END朋友们,Java 面试千千万,乐观悲观必须看!这道题不仅考察基础,还能看出你对并发编程的理解深度。希望今天的分享能让你在面试时更加胸有成竹,斩获 Offer!
你学会了吗?你更喜欢乐观锁还是悲观锁呢?欢迎在评论区讨论!
关注我,带你拿下 Java 高薪 Offer!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!