
Java常用锁机制
Java常用锁机制
乐观锁
乐观锁并不是一种具体的锁机制,而是一种并发控制策略。
乐观锁比较乐观,他认为没有人会和他抢着修改数据。因此乐观锁不会上锁,只有在提交的时候才会对数据是否冲突进行检测。如果存在冲突则会抛出一个异常(如OptimisticLockException),提示当前操作存在冲突。即,可以选择回滚当前事务实现数据准确性。
乐观锁允许多个事务或线程同时读取数据,而不会相互阻塞。
核心思想:
在数据更新时检查数据的版本号或时间戳,如果版本号或时间戳与读取时的一致,则说明数据没有被其他事务修改,可以进行更新;如果不一致,则说明数据已经被其他事务修改,此时会触发冲突。
在使用上:
乐观锁在Java中的使用是无锁编程,常常采用的是CAS机制、fail-fast等
MySQL数据库中的应用
1 | update 表 set ... ,version = version +1 where id = #{id} and version = #{version} |
操作前,先读取记录的版本号,更新时,通过SQL语句比较版本号是否一致。如果一致,则更新数据。否则会再次读取版本,重试上面的操作
使用场景:
解决超卖问题、保证库存准确性
示例——MyBatisPlus乐观锁实现借阅图书
1.创建图书库存表
1 | ##mysql |
2.注册乐观锁配置
1 |
|
3.实体类
1 |
|
@Version注解标示这个字段为版本字段,一般为Integer或Long类型,在更新数据时,MyBatis-Plus 会自动对该字段的值进行检查和递增
4.具体实现代码
1 | /** controller */ |
5.运行时返回
1 | 更新失败,数据可能已经被其他用户修改 |
同时十个线程请求,只会有一个显示更新成功
示例——原子操作类
支持在多线程环境下对整数进行操作
1 | AtomicInteger atomicInteger = new AtomicInteger(0); |
AtomicInteger使用详解
- get():获取当前AtomicInteger对象的值。
- set(int newValue):将AtomicInteger对象的值设置为指定的newValue。
- getAndSet(int newValue):先获取当前AtomicInteger对象的值,然后将对象的值设置为指定的newValue。
- compareAndSet(int expect, int update):比较当前对象的值与expect是否相等,如果相等,则将对象的值设置为update。
- getAndIncrement():获取当前AtomicInteger对象的值,并将对象的值加1。
- getAndDecrement():获取当前AtomicInteger对象的值,并将对象的值减1。
- incrementAndGet():将当前AtomicInteger对象的值加1,并返回增加后的值。
- decrementAndGet():将当前AtomicInteger对象的值减1,并返回减少后的值。
- addAndGet(int delta):将指定的delta值加到当前AtomicInteger对象的值上,并返回增加后的值。
- getAndAdd(int delta):获取当前AtomicInteger对象的值,并将指定的delta值加到对象的值上。
附:CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的同步。
CAS算法涉及到三个操作数:当前内存值 V、原始值 A、要写入的新值 B。
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。
优点:
- 减少锁的使用:乐观锁通过版本控制来避免数据冲突,减少了锁的使用,提高了并发性能。
- 减少系统开销:不需要频繁地加锁和解锁,从而减少了系统开销。
- 适用于读多写少的场景:在读多写少的场景下,乐观锁能够减少锁争用,提高系统性能。
缺点:
- 可能需要重试:当数据版本不一致时,需要回滚事务并重新尝试,这可能会增加系统负担。
- 无法防止写冲突:在高并发写操作的场景下,乐观锁可能导致更多的冲突和资源浪费。
- 版本维护:需要维护版本信息,占用额外的存储空间。
悲观锁
悲观锁比较悲观,他认为在他操作数据的时候肯定会有人和他一起抢着修改数据。所以在操作数据的时候会把数据锁住,其他的线程无法查询修改删除当前数据,必须等到锁释放之后才能继续操作。
核心思想:
在操作数据时,会把数据锁住,直到操作完成。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。
在使用上:
可以采用 JAVA 自带的 synchronized 关键字,通过添加到方法或同步块上,锁住资源。 如果是分布式系统,我们可以借助数据库自身的锁机制来实现。
1 | select * from 表名 where id = #{id} for update; |
注意:使用悲观锁的时候,我们要注意锁的级别,MySQL innodb 在加锁时,只有明确的指定主键或(索引字段)才会使用 行锁;否则,会执行 表锁,将整个表锁住,此时性能会很差。在使用悲观锁时,我们必须关闭 MySQL 数据库的自动提交属性,因为mysql默认使用自动提交模式。悲观锁适用于写多的场景,而且并发性能要求不高
使用场景:
银行系统扣款
- 感谢你赐予我前进的力量






