乐观锁和悲观锁

乐观锁

乐观认为不会有别的线程与它来抢占修改,直接对数据进行修改,在提交的时候进行检查,看是否有别的线程修改过数据,如果修改过,就放弃修改,返回错误或者重试.

乐观锁一般有两种实现方式:版本号机制和CAS算法

版本号机制(redis采用的方案)

一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

CAS算法

CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。

CAS算法会有一个巨大的问题,就是ABA问题.如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。ABA 问题的解决思路是在变量前面追加上版本号或者时间戳(java采用的方案)

CAS算法还有一个问题就是循环时间长开销大.CAS 经常会用到自旋操作来进行重试,也就是不成功就一直循环执行直到成功。如果长时间不成功,会给 CPU 带来非常大的执行开销。

乐观锁的特点:

1.乐观锁适用于并发读操作较多的场景,因为读操作不涉及到数据的修改,不需要加锁。
2.乐观锁在更新数据时,只有在提交更新操作时才对数据进行版本检查,减少了加锁和解锁的开销。
3.乐观锁可能需要进行重试,以处理并发修改引起的冲突。

悲观锁

悲观锁总认为会别的线程回来抢占修改,于是采用一种防御的姿态将数据锁住,所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。

悲观锁的特点:

1.悲观锁适用于并发写操作较多的场景,因为写操作涉及到数据的修改,需要保证数据的一致性。
2.悲观锁在加锁期间,其他线程无法访问被锁定的资源,从而保证了数据的完整性。
3.悲观锁需要频繁地进行加锁和解锁操作,开销较大。