首页 > 图灵资讯 > 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来实现。
  • 在操作共享资源时,先用synchronizedReentrantLock加锁,确保只有一个线程能访问。

举个例子:

  • 假设多个线程要修改一个共享变量count
  • 你可以用synchronized来确保每次只有一个线程可以修改这个变量。

优点

  • 能够完全避免并发冲突,因为加锁后其他线程无法访问数据。
  • 在冲突频率较高的场景下(比如多个线程频繁修改数据的场景),性能更稳定。

缺点

  • 性能较低,因为加锁会导致其他线程等待,降低系统吞吐量。
  • 如果锁没有正确释放,可能会导致死锁。

3. 选择乐观锁还是悲观锁?

  • 读多写少的场景:选择乐观锁更合适,因为大部分操作不会发生冲突,乐观锁会更高效。
  • 写多读少的场景:选择悲观锁更合适,因为冲突频率较高,悲观锁能避免频繁重试。

总结

  • 乐观锁是“先操作,再检查”,通过版本号或CAS实现,适合冲突较少的场景。
  • 悲观锁是“先加锁,再操作”,通过数据库锁或synchronized实现,适合冲突较多的场景。
上一篇 如何在Java中实现数据库的审计日志?
下一篇 返回列表

文章素材均来源于网络,如有侵权,请联系管理员删除。