多线程编程总结:四、乐观锁、悲观锁、自旋锁
在多线程编程里面我们经常会锁的使用,实际上在这个过程中,我们的锁主要分为悲观锁和乐观锁,用他们来实现多线程的并发编程控制。
悲观锁:
我们悲观的认为这一段代码或者是数据会发生资源抢占和异常,那么我们在进入这一段代码或者访问某一个数据之前就直接先加上锁,我访问的东西,其他人都不可以再访问,除非我访问完了,退出此处加锁资源。例如我们常见的Lock、Monitor、Mutex等
Lock :实际上是对Monitor的封装,一个语法糖。
try { Monitor.Enter(obj); dosomething(); } catch(Exception ex) { } finally { Monitor.Exit(obj); }
Monitor:
Monitor的常用属性和方法:
Enter(Object) 获取排他锁。
Exit(Object) 释放排他锁。
IsEntered 当前线程是否获取排它锁锁。
Wait(Object) 释放对象上的锁到等待队列并阻止当前线程,通知下一个就绪队列的线程进来。
Pulse() 当前线程不释放锁,但是通知队列中的下一个锁进入就绪队列。
PulseAll() 通知所有的等待线程对象状态的更改。
TryEnter(Object) 试图获取指定对象的排他锁。
TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
Mutex: 互斥锁,每次只能一个线程能够访问指定对象。
乐观锁:
我们乐观的认为不会发生线程不安全的问题,我们不会加锁,直到需要更新的时候再去检查我们的数据有没有被修改,如果没有修改,那么我们这边直接修改,如果有过修改,那么返回错误等用户判断如何处理。
实现乐观锁有两种方法:1. CAS算法。2.版本Version。
1. CAS算法Compare And Swap(比较与交换),要点是需要读写的内存值 D, 进行比较的值 E, 拟写入的新值 F,如果D=E,那么写入F。CAS算法有一个ABA问题,就是我们需要进行比较的值E,我们认为它一直是E,但是可能它由E变为M,然后变更为E,我们认为它还是E但是实际上它中途被变更过,为了解决这个问题我们引入了第二种方案进行控制那就是版本.
2. Version版本控制,每次我们操作上面提到的E值时,我们都将Version增加1,这样子我们去判断Version是否我变更前的版本一致由此来看是否能够提交此次修改。在C#中我们可以考虑写一个类里面增加一个Version字段来达成乐观锁,或者在数据库里面新增一列为Version来控制。
自旋锁SpinLock:
自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。