悲观锁的概念
之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设 最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当 其他线程想要访问数据时,都需要阻塞挂起。悲观并发控制实际上是“先取锁再访问”的保 守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生 额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数 据,其他事务就必须等待该事务处理完才可以处理那行数据。
悲观锁的分类
1. 共享锁【shared locks】又称为读锁,简称S锁。顾名思义,共享锁就是多个事务 对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
2. 排他锁【exclusive locks】又称为写锁,简称X锁。顾名思义,排他锁就是不能与 其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行 的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修 改。
优点:适合在写多读少的并发环境中使用,虽然无法维持非常高的性能,但是在乐观锁无法 提更好的性能前提下,可以做到数据的安全性。
缺点:加锁会增加系统开销,虽然能保证数据的安全,但数据处理吞吐量低,不适合在读多 写少的场合下使用。
乐观锁的概念
乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,只是在更新的时候 会判断一下在此期间别人有没有去更新,这个数据如果发现冲突了,则返回给用户错误的信 息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。
乐观锁的实现
1. CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁 的一种 CAS 实现方式。
2. 版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修 改的次数。当数据被修改时,version 值会+1。当线程A要更新数据值时,在读取数 据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数 据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
优点:在读多写少的并发场景下,可以避免数据库加锁的开销,提高DAO层的响应性能, 很多情况下ORM工具都有带有乐观锁的实现,所以这些方法不一定需要我们人为的去实 现。
缺点:在写多读少的并发场景下,即在写操作竞争激烈的情况下,会导致CAS多次重试,冲 突频率过高,导致开销比悲观锁更高