软件开发中的锁介绍

自旋锁

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

如果别的线程长期持有该锁,那么你这个线程就一直在 while while while 地检查是否能够加锁,浪费 CPU 做无用功。

优点:不切换上下文;

不足:烧CPU;

适用场景:冲突不多,等待时间不长的情况下,或者少次数的尝试自旋。

互斥锁

操作系统负责线程调度,为了实现「锁的状态发生改变时再唤醒」就需要把锁也交给操作系统管理。所以互斥器的加锁操作通常都需要涉及到上下文切换,操作花销也就会比自旋锁要大。

优点:简单高效;

不足:冲突等待时的上下文切换;

适用场景:绝大部分情况下都可以直接使用互斥锁。

条件锁

它解决的问题不是「互斥」,而是「等待」。

消息队列的消费者程序,在队列为空的时候休息,数据不为空的时候(条件改变)启动消费任务。条件锁的业务针对性更强。

读写锁

内部有两个锁,一个是读的锁,一个是写的锁。

如果只有一个读、一个写,那么等价于直接使用互斥锁。

不过由于读写锁需要额外记录读数量,花销要大一点。

除非是针对某种特定情景(读多写少)的「优化」,否则建议使用互斥锁替代

适用场景:读多写少,而且读的过程时间较长,可以通过读写锁,减少读冲突时的等待。

悲观锁

认为每次对数据库的操作(查询、修改)都是不安全的,因此每次操作都会把这条数据锁掉,直到本次操作完毕释放该锁。

适用场景:就是在对某个数据在处理的过程中,不允许其他人或程序或线程修改此数据,从而保证了数据修改的独占和排他性。通常大多数的悲观锁都是通过数据库的锁机制来实现的,比如:

select * from table where id = ‘xxx’ for update;

缺点:悲观锁因为独占和排他的特点,导致只有在事务提交以后才能被其他人(或程序或线程)修改。可想而知,如果并发量很大,将会导致应用程序在数据库处理层面阻塞而变得十分缓慢,并发量严重降低。

乐观锁

查询数据的时候总是认为是安全的,不会锁数据;等到更新数据的时候会判断这个数据是否被人修改过,如果有人修改过了则本次修改失败。

每个人(或程序或线程)都可以去读取并修改这个数据,但是在修改完成后,对提交才做限制,只有满足一定条件的数据才可以被修改(提交)。乐观锁的实现,一般我们通过对数据库表加一个version字段,当对某条记录修改后,同时对version(old)+1,然后把看version(new)的值是否大于数据库当前version(DB)的值,如果大于则提交,否则说明其他人(或程序或线程)已经修改过本条数据(version是old),只能继续读取数据库最新的数据重复之前的操作。

update table set name=‘xxx’ ,version = version(old)+1 where version = version(old) and id=‘xxxxxx’;

比如这个sql,如果返回大于1说明数据被更新了,则说明数据从读取出来后就没有被其他人(或程序或线程)修改过,否则如果返回0,则说明,数据已经被修改。

缺点:当数据的修改操作发生的比较频繁,乐观锁的效率就会很低,因为每次读取后更新将基本上都不成功。

分布式锁

为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。

分布式锁应该具备以下条件:

1、分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程调用;2、高可用的获取锁与释放锁;3、高性能的获取锁与释放锁;4、具备可重入特性;5、具备锁失效机制,防止死锁;6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

分布式锁的实现方法:

Memcached:利用 Memcached 的add命令。此命令是原子性操作,只有在key不存在的情况下,才能add成功,也就意味着线程得到了锁。Redis:和 Memcached 的方式类似,利用 Redis 的setnx命令。此命令同样是原子性操作,只有在key不存在的情况下,才能set成功。Zookeeper:利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。Zookeeper 设计的初衷,就是为了实现分布式锁服务的。Chubby:Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法。

共享锁(来自数据库机制)

共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。简单的说,就是多个事务只能读数据不能改数据。

排他锁(来自数据库机制)

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

偏向锁

在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。

那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步,退出同步也,无需每次加锁解锁都去CAS更新对象头,如果不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候需要锁膨胀为轻量级锁,才能保证线程间公平竞争锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值