悲观锁
悲观锁并发控制实际上是 先取锁再访问 的保守策略,指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理) 修改保持保守态度(悲观),因此在整个数据处理过程中,将数据处于锁定状态。
优点
为数据处理的安全提供了保证
缺点
处理加锁的机制会让数据库产生额外的开销,还会增加产生死锁的机会。在只读型事务处理中由于不会产生冲突,没必要使用锁,会增加系统负载,还有降低并行性。
乐观锁
假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测(基于CAS),如果冲突了,则返回错误信息,由用户觉得抛错还是重试。
优点
没有任何的锁与死锁
缺点
只适用与并发写比较少的场景,当有较大概率出现数据竞争时,容易更新报错,可能由于客户端的重试导致系统的负载加重。另外由于乐观锁大多采用硬件支持的 CAS机制(compare and set), 容易出现 ABA问题(内存数据由初始的A,被多个事务更改成B之后又改回了A, 那么CAS指令认为这条数据没有发生改变,从而执行更新操作,有可能导致数据的覆盖)。
互斥锁
用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁
自旋锁
如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。
优点
自旋锁不会引起调用者睡眠, 因此不会导致内核状态切换(挂起线程和恢复线程的操作都需要在内核态中完成)
缺点
- 自旋锁一直占用CPU,如果短时间无法获取锁,将会降低CPU使用效率
- 在使用自旋时很有可能造成死锁,如递归调用
偏向锁
轻量级锁
锁消除
锁粗化
原则上将同步块的作用范围限制得尽量小-只在共享数据的实际作用域中才进行同步,为了尽量拿到锁。
但是如果一系列的操作对象都是对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁的进行互斥同步也会导致不必要的性能损耗。JVM会将加锁同步的范围粗化。
公平锁
每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁
行锁
表锁
排它锁
排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
1 | SELECT ... FOR UPDATE; |
共享锁
共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
1 | SELECT ... LOCK IN SHARE MODE; |