Java 并发编程 —— 悲观锁与乐观锁
简介
为避免多线程环境下,并发事务造成ACID错误,更合理的使用Spring 事务隔离属性,这篇文章主要介绍如何通过悲观锁与乐观锁来限制Spring事务隔离属性使用不当的问题。
悲观锁
悲观锁(Pessimistic Lock) —— 比较悲观的锁。
认为修改数据时存在着其它连接也想修改此数据的事务。
介绍
- 悲观锁每次获取数据都要锁数据
(共享资源每次只给一个线程使用)
- 另外的线程获取此数据时是block,直到数据被解锁才可以使用
(其它线程阻塞,用完后再把资源转让给其它线程)
- 常见的悲观锁实现(数据库层面,在以下操作前会锁住数据)
- 行锁
- 表锁
- 读锁
- 写锁
- Java(程序层面,重入锁与同步锁都是悲观锁的一种实现)
- ReentrantLock(重入锁)
- synchronized(同步锁)
如何使用
数据库行锁
FOR UPDATE
# 可在MyBatis使用的时候在SQL中将数据库记录锁住(行锁)
SELECT CLOUMN_A, CLOUMN_B, CLOUMN_C FROM TABLE WHERE CLOUMN_A = 'PARAM_A' FOR UPDATE
# 上面的SQL锁住了TABLE中条件为(CLOUMN_A = 'PARAM_A')的记录。此事务提交前(提交后会释放行锁),其它连接无法使用。
# FOR UPDATE:是行锁的一个关键字,会锁住这张表的这条记录,事务提交之后,才允许其它连接使用。
Hibernate 悲观锁实现
String sql = "SELECT CLOUMN_A, CLOUMN_B, CLOUMN_C FROM TABLE WHERE CLOUMN_A = 'PARAM_A'";
Query query = session.createQuery(sql);
query.setLockMode("SQL_OBJ",LockModel.UPGRADE);
Hibernate 加锁模式
- Hibernate内部使用使用锁
- LockMode.NONE
无锁机制 - LockMode.WRITE
Insert和Update记录的时候会自动获取 - LockMode.READ
在读取记录时会自动获取
- LockMode.NONE
- 数据库控制锁
- LockMode.UPGRADE
利用数据库的for update字句加锁
- LockMode.UPGRADE
注意事项
使用悲观锁,必须关闭MySql数据库自动提交属性,MySql默认使用autocommit模式,当执行一个更新操作后,MySql会立刻将结果进行提交。 set autocommit = 0;
使用START TRANSACTION,自动提交将保持禁用状态,直到使用COMMIT或ROLLBACK结束事务。自动提交模式然后恢复到之前的状态(如果start transaction 前 autocommit = 1,则完成本次事务后autocommit 还是1 。 如果start transaction 前 autocommit = 0, 则完成本次事务后 autocommit 还是0)
乐观锁
乐观锁(Optimistic Lock) 每次取数据的时候都认为别人不会修改,所以不锁。
认为在短暂的时间里不会有事务来修改此数据库的数据!
介绍
- 大多基于数据版本(Version)
- 数据版本:数据增加一个版本标识,基于数据库表的版本解决方案中,一般是通过为数据表增加一个“version”字段来实现
- CAS算法实现 (点击打开 Java算法 —— CAS实现)
- Java 实现
- java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
如何使用
版本标识
- 读取数据时,读取此数据的版本号
- 更新时,对此版本号加一
- 提交对版本数据与数据库表对应记录的当前版本信息进行对比
- 如果提交的版本号大于数据库当前版本,则更新,否则认为版本过期,事务回滚报错
注意事项
适用于多读应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。