悲观锁(Pessimistic Concurrency Control):顾名思义,就是很悲观,每次获取共享资源的时候都认为会产生冲突,所以会在此之前加锁。每次加锁前都假定会发生冲突,传统关系型数据库中包含很多这样的机制,例如读锁、写锁、行锁、表锁等。当一个线程无法获取到锁时(此时的锁在另一个线程),则该线程将会被阻塞,直到其他线程释放锁,该线程获取到锁为止
乐观锁:顾名思义,比较乐观,每次获取共享资源的时候都认为不会产生冲突,所以在此之前不会加锁,而是在更新的时候去判断一下在此期间是否有其它线程对需要访问的共享资源进行了修改,使用版本号等机制进行管理。乐观锁多用于多读少写的应用类型,这样可以减少锁的竞争和阻塞,从而提高吞吐量和并发性能。但是如果线程冲突频繁,乐观锁将会导致多次访问而产生额外开销。不同于悲观锁,乐观锁是人为控制的。
优缺点
悲观锁
优点:在线程竞争激烈时可以保证资源的有序访问
缺点:一个线程用悲观锁对数据加锁后,其它线程将不能对加锁的线程进行操作(有时可以进行读操作),如果持有锁的任务执行过程很长,那么将其它线程将一致等待,影响系统的性能
乐观锁
优点:基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能
缺点:乐观锁是认为实现的,仅适用于当前业务范围,如果有其它外来事务访问,将可能产生问题
悲观锁:因为悲观锁会影响系统吞吐的性能,所以适合应用在写为居多的场景下。
乐观锁:因为乐观锁就是为了避免悲观锁的弊端出现的,所以适合应用在读为居多的场景下。
乐观锁的实现
常见的方法包括版本号机制和CAS算法实现
版本号机制:数据字段加上一个版本version,表示数据更新的次数,更新一次就加一。更新数据时,读取一次version,提交更新操作时,再读取一次,需要保证两次的版本值相同。
举个例子:一个银行账户有1000元,当前version字段为0
1.操作员A先读出账户1000与版本号1,计算出需要的更新的操作账户(900 = 1000 - 100),版本号(1 = 0+1)
2.操作员B后读出账户1000与版本号1,计算出需要的更新的操作账户(900 = 1000 - 200),版本号(1 = 0+1)
3.操作员A提交更新操作,由于更新版本值1大于当前数据库记录的版本值0,所以更新成功
4.操作员B提交更新操作,但是更新版本值1并不大于当前数据库记录的版本值1,所以更新失败
CAS算法(compare and swap)
是一种无锁算法,不使用锁的情况下实现多线程之间的同步问题,也就是在没有线程阻塞的情况下实现同步,所以叫非阻塞同步
CAS算法操作数
需要读V
进行比较的值A
拟写入的新值B
过程:读取到值V时,检查原来的值是否还为V,当且仅当V的值等于A时,CAS通过原子的方式用新值B来更新V的值,否则将自旋,CAS利用CPU指令,从硬件层面保证了操作的原子性,以达到类似于锁的效果
但是CAS有一个问题那就是会产生ABA问题,ABA问题就是在比较更新之前,进行了两次或以上操作,数据保持不变,也就是假如数据V将要进行Z操作读取之后、更新之前,被X操作增加了1,又被Y操作减少了1,此时V是被修改过的,但是Z操作检测不到,于是忍仍然对数据进行了更新。
ABA 问题解决:
我们需要加上一个版本号(Version),在每次提交的时候将版本号+1操作,那么下个线程去提交修改的时候,会带上版本号去判断,如果版本修改了,那么线程重试或者提示错误信息