• 操作系统中的锁的分类


    参考:https://mp.weixin.qq.com/s/9zRmjH5Bgzo-EDIzZ5C7Hg

    操作系统中的锁分为两大类:悲观锁和乐观锁。

    1. 悲观锁

    悲观锁,Pessimistic Lock,即这种锁的“想法”很悲观——方法执行如果不加锁就会出事,所以操作必须上锁,一个一个的来。

    其中重量级锁、自旋锁自适应自旋锁属于悲观锁。

    1.1 重量级锁

    当进入一个同步、线程安全的方法时,需要先获得该方法的锁,而退出这个方法时,则需要释放锁。如果线程A获取不到该锁,则意味着该方法有别的线程在执行,这时线程A会马上进入阻塞状态,直到持有锁的线程释放锁,才会从阻塞状态被唤醒,再尝试去获取该方法的锁。

    重量级锁的特点是:获取不到锁,马上进入阻塞状态

    由于重量级锁的特点,导致了它的效率有时候会很慢。试想,当线程A发现锁被占用,立即进入阻塞状态,之后的0.0001秒,这个锁便被释放了,那么它又会从阻塞状态进入运行状态。我们都知道,线程从运行状态进入阻塞状态,需要保存线程的执行状态、上下文等数据,以及设计到用户态到内核态的转换,非常耗时,同样从阻塞状态到运行状态也是一样的道理。因此重量级锁这个马上进入阻塞状态的特点,往往会耗费很多的时间在线程的状态转换中。

    1.2 自旋锁

    自旋锁是在重量级锁的基础上,做出了一些改进,它在线程判断方法有别的线程执行之后,不会立马进入阻塞状态,而是等待一段时间,也就是在一段固定的循环时间内,看看这个锁有没有被释放。如果一直没有被释放,线程才会进入阻塞状态。

    自旋锁的特点是:获取不到锁,等一段固定时间,再进入阻塞状态

    1.3自适应自旋锁

    自适应自旋锁是在自旋锁的基础上,对一段固定时间作出了一些调整:不需要人为去指定这一段固定时间究竟是多久,而是根据线程最近获得锁的状态来调整循环次数,尽量去减少等待时间,从而减少CPU的消耗。

    2. 乐观锁

    乐观锁,Optimistic Lock,即这种锁的“想法”很乐观——方法执行不用加锁,要是出现了冲突再想办法去解决。

    不加锁,就应该用其他的方法去控制方法的同步和线程安全,也就是CAS机制。

    2.1 CAS机制
    CAS,Compare and Swap,比较并替换。CAS机制中采用了3个基本操作数,分别是内存地址V、旧预期值A、新值B。当线程在更新一个变量的时候,只有当变量的旧预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

    这里有一个例子来说明一下CAS机制:
    有两个线程A、B,都想要将内存地址V当中的值加1,内存地址V当中的初始值为10。
    1、对于线程A来说,旧预期值A=10,新值B=11;
    2、而当线程A提交更新之前,线程B抢先一步,将内存地址V当中的值加1变成了11;
    3、此时,线程A开始提交更新,将旧预期值A与内存地址V当中的值进行比较,发现不等,提交失败;
    4、线程A重新尝试获取旧预期值A=11,新值B=12,该重新尝试的过程被称为自旋;
    5、线程A提交更新,旧预期值A与内存地址V当中的值相同,提交更新,此时内存地址V当中的值更新为B,也就是12。

    CAS的缺点:
    1) CPU开销过大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
    2) 不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
    3) ABA问题:这是CAS机制最大的问题所在。通常会采取加入版本号,从而有效的解决ABA问题。

    其中轻量级锁偏向锁属于乐观锁,并使用CAS机制来对方法进行相应的标记,从而保证方法的同步和线程安全。

    2.2 轻量级锁

    轻量级锁会认为,很少有线程刚好也来执行相同的方法,所以,当进入一个方法的时候根本不用加锁,只需要做一个标记,也就是一个变量,来记录此时该方法是否有人在执行。当该方法没有在执行的时候,线程进入该方法,采用CAS机制,将方法的状态标记为正在执行,当退出方法的时候,再将状态标记为没有在执行。使用CAS机制来改变状态,是因为对状态的改变并不是原子操作,所以会使用CAS机制来保证操作的原子性。

    轻量级锁适合很少出现多个线程竞争一个锁的情况,即多个线程总是错开时间来获取锁的情况。

    2.3 偏向锁

    如果线程是首次执行该方法,那么便会采用CAS机制将其标记为有人在执行,同时会将线程ID记录进去,以标记是哪个线程正在执行。而当线程退出该方法的时候,不会改变其状态,而是直接退出,因为其默认除了本线程,其他线程并不会执行该方法。这样当这个线程想要再次执行该方法的时候,会判断其状态,如果已经被标记为有人在执行并且线程的ID是自己,那么就直接执行方法。

    偏向锁适用于始终只有一个线程在执行一个方法的情况。

    总结

    两类锁并没有孰好孰坏之分,因为其不同的“想法”,分别会有着它们最合适的应用条件和场景。由于悲观锁的“想法”比较悲观,认为不加锁就会出错,因此会阻塞事务。一般来说悲观锁会应用于经常发生冲突的时候,通俗来讲可以理解为写多读少的情况。而乐观锁由于“想法”比较乐观,认为没有必要加锁,因此往往会回滚重试。一般来说乐观锁会应用于冲突较少发生的时候,通俗理解为读多写少的情况,这样就可以省去锁的开销,从而加大系统的吞吐量。

  • 相关阅读:
    spark系列-6、对Application,Driver,Job,Task,Stage的理解
    spark系列-5、RDD、DataFrame、Dataset的区别和各自的优势
    spark系列-4、spark序列化方案、GC对spark性能的影响
    spark系列-2、Spark 核心数据结构:弹性分布式数据集 RDD
    nginx学习(九):跨域配置和防盗链配置
    nginx学习(八):nginx配置gzip
    nginx学习(七):nginx提供静态资源服务
    nginx学习(六):日志切割
    nginx学习(五):nginx.conf 核心配置文件详解
    nginx学习(四):nginx处理web请求机制
  • 原文地址:https://www.cnblogs.com/yspworld/p/13084965.html
Copyright © 2020-2023  润新知