详解 Java 的悲观锁与乐观锁
AI 概述
一、前言:为什么要有“锁”?二、悲观锁:假设冲突一定会发生1. 核心思想2. 生活例子:图书馆借书3. Java 中的悲观锁实现4. 代码示例:用悲观锁解决售票问题5. 悲观锁的优缺点三、乐观锁:假设冲突很少发生1. 核心思想2. 生活例子:在线文档协作3. Java 中的乐观锁实现4. 代码示例:用乐观锁实现计数器5...
目录
文章目录隐藏

一、前言:为什么要有“锁”?
在日常生活中,我们经常会遇到资源共享的问题:
- 图书馆借书:同一本书不能同时借给两个人;
- 抢火车票:12306 需要确保同一张票不会卖给多个人;
- 多人编辑文档:需要避免后保存的内容覆盖先保存的内容。
这些场景的共同点是:多个主体同时访问和修改同一个资源。在 Java 并发编程中,这就是 “多线程并发访问共享数据” 的问题。
为了解决这个问题,我们需要一种机制来确保数据操作的安全性,这种机制就是锁。
二、悲观锁:假设冲突一定会发生
1. 核心思想
悲观锁像一位谨慎的保安,它默认 “一定会发生并发冲突”,因此在操作资源前,先把资源锁住,防止其他线程访问,直到自己操作完成后才释放锁。
2. 生活例子:图书馆借书
- 你去图书馆借《Java 编程思想》,系统显示 “可借”;
- 你点击 “借书”,系统立即锁定这本书;
- 此时同事也来借这本书,系统提示 “已被借出,请等待归还”;
- 你看完还书后,系统释放锁,同事才能借走。
3. Java 中的悲观锁实现
Java 中常用的悲观锁实现:
- synchronized 关键字(隐式锁,JVM 自动管理);
- ReentrantLock 类(显式锁,需要手动加锁和释放)。
4. 代码示例:用悲观锁解决售票问题
import java.util.concurrent.locks.ReentrantLock;
publicclass PessimisticLockDemo {
// 总票数
privatestaticint ticketNum = 100;
// 显式锁
privatestaticfinal ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
// 模拟 10 个窗口同时售票
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
// 加锁
lock.lock();
try {
if (ticketNum > 0) {
System.out.println(Thread.currentThread().getName() +
" 卖出第 " + ticketNum + " 张票");
ticketNum--;
} else {
break; // 票卖完了
}
} finally {
// 释放锁
lock.unlock();
}
try {
Thread.sleep(50); // 模拟售票操作时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "窗口" + (i + 1)).start();
}
}
}
5. 悲观锁的优缺点
- 优点:实现简单,保证数据一致性;
- 缺点:效率低,线程需要等待锁释放,可能导致性能瓶颈;
- 适用场景:写操作多、并发冲突频繁的场景。
三、乐观锁:假设冲突很少发生
1. 核心思想
乐观锁像一位信任他人的管理员,它默认 “并发冲突很少发生”,因此操作资源时不锁定,只在提交修改时检查是否有其他线程修改过该资源。
2. 生活例子:在线文档协作
- 你和同事同时打开一份文档进行编辑;
- 你们各自编辑自己的部分,互不干扰;
- 你先保存,系统检查到文档自你打开后未被修改,保存成功;
- 同事保存时,系统检测到文档已被修改,提示 “存在冲突,是否合并?”。
3. Java 中的乐观锁实现
Java 中乐观锁的核心是 CAS(Compare and Swap,比较并交换) 机制:
- 给变量增加一个版本号或时间戳;
- 更新时比较版本号,如果一致则更新,否则说明被修改过,需要重试。
Java 提供了java.util.concurrent.atomic包,里面的类(如 AtomicInteger)都是基于 CAS 实现的乐观锁。
4. 代码示例:用乐观锁实现计数器
import java.util.concurrent.atomic.AtomicInteger;
publicclass OptimisticLockDemo {
// 使用 AtomicInteger 实现乐观锁
privatestaticfinal AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 模拟 10 个线程同时累加计数
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// 原子自增操作
counter.incrementAndGet();
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
System.out.println("最终计数结果: " + counter.get());
}
}
5. 乐观锁的注意点
- ABA 问题:变量被修改为 B 后又改回 A,CAS 会误以为没有变化;
- 自旋开销:如果冲突频繁,会导致多次重试,浪费 CPU 资源。
6. 乐观锁的优缺点
- 优点:无阻塞,性能高,适合读多写少的场景;
- 缺点:实现复杂,需要处理重试和 ABA 问题;
- 适用场景:读操作多、并发冲突少的场景。
四、悲观锁 vs 乐观锁:对比总结
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
六、总结
悲观锁和乐观锁各有优缺点,没有绝对的好坏之分,关键在于根据具体场景选择合适的锁机制。
作为 Java 开发者,理解这两种锁的原理和适用场景,不仅能帮助你写出更高效的并发代码,还能在面试中脱颖而出。
以上关于详解 Java 的悲观锁与乐观锁的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 详解 Java 的悲观锁与乐观锁
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 详解 Java 的悲观锁与乐观锁
微信
支付宝