乐观锁
定义
在操作时持乐观态度,认为操作时其它线程不会修改数据,因此不会锁定数据,但是在更新数据时会用版本号或者CAS算法判断数据在本次操作过程中是否被更改,如果被更改,则修改失败。
所以乐观锁虽然名字带锁,但是实际上并不会对数据进行锁定操作,其它线程仍然可以自由地读写数据,不会造成死锁等问题。
适用场景
乐观锁不会锁定数据,所以其它需要读取数据的线程不需要等待,但是如果更新频繁,频繁地出现修改失败回滚的情况,反而会导致性能及使用体验的问题。
因此乐观锁适用于需要频繁读取数据,但是较少更新数据的场景。
实际应用
Java中原子变量使用了CAS算法来避免加锁,同时保证数据修改的正确性:
-
使用volatile声明该变量为线程间共享变量;
-
使用CAS算法保证变量修改的正确性,我们可以调用compareAndSet函数来尝试修改原子变量,该函数会返回修改的结果,如果成功,则操作完成,如果失败则获取新值处理后继续尝试;
实现方式
乐观锁通常有两种实现方式:
通过版本号机制实现;
通过CAS(compare and swap)算法实现;
版本号机制实现
- 取出数据时同时获取数据当前的version;
- 更新数据时也带上这个version;
- 服务端比对请求的version与目前数据的version是否一致;
- 如果一致,则更新数据,如果不一致,则修改失败;
CAS算法实现
CAS算法简介
CAS是compare and swap的缩写,算法逻辑就是当一个线程要修改某个数据时,需要携带上之前的原数据,服务端会将这个原数据与当前数据进行比对,如果一致,则将当前数据替换为申请修改的数据,如果不一致,则修改失败。
ABA问题
- 线程T1与T2均取出A数据;
- T1要将A数据更新为B,而此时T2速度更快,将数据从A更新为了C,然后又将C更新为了A;
- T1的更新请求到达服务端,服务端比对T1携带的原数据A与当前数据A,结果相等,于是将数据更新为了B,但实际数据已经几经变化了;
在大多数情况下,这都是没有问题的,但如果我们需要严格保证原数据未经变动时,则可能出现问题,相比较使用版本号实现的乐观锁则可以避免此问题。
悲观锁
定义
在操作时持悲观态度,认为其它线程会修改数据,所以更新数据时对数据进行锁定,加锁后,同一时间只能有一个线程执行,其它线程只能等待直到锁被其它线程释放。
适用场景
悲观锁可以保证数据操作时的正确性,不会出现数据因被其它线程修改而失败的情况,但是它会限制其它线程读取数据。
所以悲观锁更适用于修改频繁,使用乐观锁会频繁冲突回滚,但读取较少的场景。
实际应用
Java的synchronized关键字就是一种悲观锁