乐观锁和悲观锁, 是并发控制了两种手段, 是一种思想.
悲观锁(Pessimistic Lock)
在修改数据之前先锁定, 再修改.
之所以叫做悲观锁,是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。
悲观锁的实现,往往依靠数据库提供的锁机制
共享锁/排它锁
悲观锁又可以分为共享锁和排它锁
- 共享锁(读锁): 多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
- 排它锁(写锁): 如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。
Mysql中实现悲观锁
- 读锁
select …. in share mode
- 写锁
select … for update
update语句自动加写锁
乐观锁( Optimistic Locking )
在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。
乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。一般的实现乐观锁的方式就是记录数据版本。

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
CAS
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
ABA问题
假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。(也可以使用时间戳, 因为时间戳具有天然的递增性)
java中的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package concurrency;
import java.util.concurrent.atomic.AtomicStampedReference;
/** * @Author wenxuan.hao * @create 2020-02-15 00:14 */ public class CAS_ABA { private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);
public static void main(String[] args){ Thread main = new Thread(() -> { System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference()); int stamp = atomicStampedRef.getStamp(); //获取当前标识别 try { Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行 } catch (InterruptedException e) { e.printStackTrace(); } boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1); //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败 System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess); },"主操作线程");
Thread other = new Thread(() -> { Thread.yield(); // 确保thread-main 优先执行 atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1); System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference()); atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1); System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference()); },"干扰线程");
main.start(); other.start(); }
}
|