锁,自下到上的总结

一、锁的本质

所谓的,其实本质上就是一块存储了标记位的内存空间
当这个空间被赋值为1的时候表示加锁了,赋值为0的时候表示解锁了。多个线程抢一个锁,就是抢着要把这块内存空间赋值为1。那我们要做的就是保证一次只有一个线程可以抢到锁
在单核环境中,我们只需要在加锁之前,关闭中断,加锁完成后,打开中断。就可以保证加锁过程的原子性,只有一个线程可以抢到锁。
在多核环境中,内存空间是共享的,每个核上各跑一个线程。此时我们要保证一次只有一个线程抢到锁,就需要硬件层面的某种支持。

二、锁的硬件支持

其实加锁的过程就是一个判断并赋值的过程。先判断是否为0,为0说明可以加锁,然后赋值为1,获得锁。
关键在于,必须保证判断和赋值这两个语句作为一个整体的原子性。否则可能会出现A线程判断为0,尚未加锁的时候,B线程读取了值判断为0,也进行加锁。最后A、B两个线程,都认为自己成功获得锁,就导致了错误。
保证原子性有两种事项方法,一种是纯软件层面的实现,另一种是硬件层面的实现。
软件层面的实现就是通过一些特定的算法,加上内存屏障,来保证了原子性。(内存屏障可以保证屏障内指令的执行顺序,不会被CPU乱序执行)
硬件实现的话,则是CPU提供了原子性的CMPXCHG操作,用于实现最底层的CAS锁。

三、硬件层面锁的实现原理(CAS锁,Compare and Swap/Set)

CAS操作的意思就是说,比较并交换,或者比较并赋值。
CPU通过三种方式来保证CMPXCHG操作的原子性,依次升级(开销也随之变大)

第一种方式:关中断。

只有在单核平台下才可以使用

第二种方式:锁缓存。

锁缓存基于Ringbus+MESI缓存一致性协议实现,且有三个使用前提。该方法是从锁总线优化而来。

  1. 操作的数据已经被缓存在CPU内部。
  2. 操作的数据不能跨越多个缓存行(缓存行大小一般为64字节)。
  3. 处理器要支持锁缓存操作

MESI大致的意思是:若干个CPU核心通过ringbus连到一起。每个核心都维护自己的Cache的状态。如果对于同一份内存数据在多个核里都有cache,则状态都为S(shared)。一旦有一核心改了这个数据(状态变成了M),其他核心就能瞬间通过ringbus感知到这个修改,从而把自己的cache状态变成I(Invalid),并且从标记为M的cache中读过来。同时,这个数据会被原子的写回到主存。最终,cache的状态又会变为S。
这相当于给cache本身单独做了一套总线(要不怎么叫ring bus),避免了真的锁总线。

第三种方式:锁总线。

某一个核心触发总线的“Lock#”那根线,让总线仲裁器工作,把总线完全分给该核心。其余的CPU核心不能再通过总线与内存通讯。从而达到“原子性”的目的。但会导致所有其他核心无法访问内存,十分低效。

四、软件层面基础锁的实现原理

1.轻量级锁(基于CAS,又称无锁、乐观锁、自旋锁,是并发类型的锁)
(1)实现原理:当前线程A要进行操作时,先读取操作数据E,计算出结果V,然后再次读取该操作数据N,检验E与N是否相等。如果相等,则认为在我读取计算期间,没有其他线程修改了该数据,那么将V写回,操作完成。反之从头再进行一次相同的操作,直到成功为止(即使用了while循环)。
(2)优点:
① 自旋锁不会使线程状态发生切换,不会使线程进入阻塞状态,减少了不必要的上下文切换
② 在并发量不高的时候可以大幅度提升效率(约是重量级锁的10倍)。
(3)缺点:
① 在线程竞争激烈的时候,会导致很多CPU一直自旋空转,降低系统的效率。
(4)适用条件:获得锁后处理的事务不繁重,且对该锁的竞争不激烈时,适合适用CAS锁。不要在单核系统里面使用自旋锁
(5)ABA问题:通过增加版本号或者一个布尔变量作为校验解决。
(6)原子性问题:CAS锁中比较并写回的操作,必须要保持原子性,该原子性由CPU的lock CMPXGCH指令保证。
2.重量级锁(悲观锁,又称互斥锁)

五、软件层面高层锁的实现

读写锁(共享-独占锁)

读写锁又称共享-独占锁,其含义是读共享的,写独占的锁。
读写锁的特性:

  1. 当加了写锁的时候,其他线程加写锁或读锁都会被阻塞(不是失败)
  2. 当加了读锁的时候,其他线程加写锁会阻塞,加读锁会成功。

适用场景:对数据的读远大于写的情况。

DCL(Double Check Lock)双重检查锁。

例如多线程下的懒汉式单例模式,通常就是使用DCL实现的,DCL是一种非常常用的设计。使用DCL的时候一定要对单例对象附带volatile关键字。(Java语言)
所谓双重检查锁,一共有三个步骤。
第一步,验证锁定条件(第一次检查),如果满足,才进行锁定,反之不锁定。
作用:第一次检查是为了降低系统开销,如果不经过任何检查直接加锁的话,开销是很大的。(加锁会降低100倍或更高的性能)
第二步,加锁。
第三步,再次检查锁定条件(第二次检查)
作用:第二次检查,是为了避免当前线程(例如叫做A),在完成第一次检查后,尚未加锁时,其他线程(如B),完成第一次检查并上锁,完成了操作。
此时B完成后,A才获得锁,但是操作实际上已经完成了。如果不做第二次检查,会导致操作被A重复多做了一次,造成了错误。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值