首页 > 图灵资讯 > java面试题>正文
解释Java中的乐观锁与悲观锁的实现原理
2025-03-06 09:34:53
1. 什么是乐观锁?
乐观锁就像一个乐观的人,认为大部分时候不会有冲突,所以它不会主动加锁,而是通过一种“检测机制”来判断是否真的发生了冲突。
原理:
- 乐观锁的核心思想是“先操作,再检查”。
- 每次操作数据时,先读取数据的版本号(或者时间戳)。
- 修改数据时,再检查数据的版本号是否还是原来的。如果版本号没有改变,说明没有其他线程修改过数据,可以安全地提交操作;如果版本号改变了,说明有冲突,就需要重新尝试操作。
实现方式:
在Java中,乐观锁通常通过**CAS(Compare-And-Swap,比较并交换)**机制来实现。CAS是一种无锁的操作,它会比较当前值和期望值是否一致,如果一致就更新,否则就失败。
举个例子:
- 假设你有一个商品库存,每次修改库存时,都会记录一个版本号。
- 当线程A读取库存时,它也会读取当前的版本号,比如版本号是
1
。 - 线程A尝试更新库存时,会检查版本号是否还是
1
。如果版本号还是1
,说明没有其他线程修改过库存,线程A就可以安全地更新库存并将版本号改为2
。 - 如果版本号已经变成
2
,说明另一个线程已经修改过库存,线程A就需要重新读取数据并尝试更新。
优点:
- 不需要真正加锁,性能较高,尤其是在读多写少的场景下(比如电商系统中浏览商品详情的场景)。
- 不会出现死锁问题。
缺点:
- 如果并发冲突频率较高,可能会导致频繁的重试,反而降低性能。
2. 什么是悲观锁?
悲观锁就像一个悲观的人,认为每次操作数据时都可能发生冲突,所以它会主动加锁,确保其他线程无法同时修改数据。
原理:
- 悲观锁的核心思想是“先加锁,再操作”。
- 每次操作数据时,先给数据加锁,确保其他线程无法访问或修改。
- 操作完成后,释放锁。
实现方式:
在Java中,悲观锁通常通过数据库的锁机制或者Java的同步锁来实现。
(1)数据库锁:
- 悲观锁可以通过数据库的
SELECT ... FOR UPDATE
语句来实现。 - 当一个线程执行
SELECT ... FOR UPDATE
时,数据库会给查询的那条数据加锁,其他线程无法修改这条数据,直到锁被释放。
举个例子:
- 假设你有一个商品库存表,线程A要修改库存时,会执行
SELECT ... FOR UPDATE
。 - 数据库会给这条库存记录加锁,其他线程如果尝试修改这条记录,就会被阻塞,直到线程A完成操作并释放锁。
(2)Java同步锁:
- 悲观锁可以通过Java的
synchronized
关键字或者ReentrantLock
来实现。 - 在操作共享资源时,先用
synchronized
或ReentrantLock
加锁,确保只有一个线程能访问。
举个例子:
- 假设多个线程要修改一个共享变量
count
。 - 你可以用
synchronized
来确保每次只有一个线程可以修改这个变量。
优点:
- 能够完全避免并发冲突,因为加锁后其他线程无法访问数据。
- 在冲突频率较高的场景下(比如多个线程频繁修改数据的场景),性能更稳定。
缺点:
- 性能较低,因为加锁会导致其他线程等待,降低系统吞吐量。
- 如果锁没有正确释放,可能会导致死锁。
3. 选择乐观锁还是悲观锁?
- 读多写少的场景:选择乐观锁更合适,因为大部分操作不会发生冲突,乐观锁会更高效。
- 写多读少的场景:选择悲观锁更合适,因为冲突频率较高,悲观锁能避免频繁重试。
总结:
- 乐观锁是“先操作,再检查”,通过版本号或CAS实现,适合冲突较少的场景。
- 悲观锁是“先加锁,再操作”,通过数据库锁或
synchronized
实现,适合冲突较多的场景。
