文章目录
锁机制
常用的锁机制有两种:悲观锁、乐观锁
悲观锁
- 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
- 悲观锁的实现,往往依靠底层提供的锁机制。
- 悲观锁会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁
-
假设不会发生并发冲突,每次不加锁而是假设没有冲突而去完成某项操作,只在提交操作时检查是否违反数据完整性。
-
如果因为冲突失败就重试,直到成功为止。
-
乐观锁大多是基于数据版本记录机制实现。
-
为数据增加一个版本标识,比如在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
-
此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
-
乐观锁的缺点是不能解决脏读的问题。
-
在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题。
-
如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法。
锁机制存在的问题
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 一个线程持有锁会导致其它所有需要此锁的线程挂起。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
两种锁总结
- 独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
- 所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
CAS(Compare & Set/Compare & Swap)
CAS是解决多线程并行情况下使用锁造成性能损耗的一种机制。
- CAS操作包含三个操作数——内存位置(V)、预期原值(A)、新值(B)。
- 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。
- 否则,处理器不做任何操作。
- 无论哪种情况,它都会在CAS指令之前返回该位置的值。
- CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
一个 CAS 涉及到以下操作:
假设内存中的原数据V,旧的预期值A,需要修改的新值B
- 比较 A 与 V 是否相等
- 如果比较相等,将 B 写入 V
- 返回操作是否成功
CAS算法原理描述
- 在对变量进行计算之前(如 ++ 操作),首先读取原变量值,称为 旧的预期值 A
- 然后在更新之前再获取当前内存中的值,称为 当前内存值 V
- 如果 A==V 则说明变量从未被其他线程修改过,此时将会写入新值 B
- 如果 A!=V 则说明变量已经被其他线程修改过,当前线程应当什么也不做。
- 用C语言来描述该操作
看一看内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。
int compare_and_swap (int* reg, int oldval, int newval)
{
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
return old_reg_val;
}
- 变种为返回bool值形式的操作
返回 bool值的好处在于,调用者可以知道有没有更新成功
bool compare_and_swap (int *accum, int *dest, int newval)
{
if ( *accum == *dest )
{
*dest = newval;
return true;
}
return false;
}
C/C++程序中,CAS的各种实现版本
- GCC的CAS,GCC4.1+版本中支持CAS的原子操作。