zoukankan      html  css  js  c++  java
  • 锁的基本分类

    Java中实现锁的方式有多种,并且锁的分类也有很多,这篇文章会从锁分类方面简单介绍各分类的锁的特点。

    公平锁和非公平锁 

        公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照锁请求的FIFO的规则从队列中取到自己。在现实中,公平保证了锁是非常健壮的锁,有很大的性能成本。要确保公平所需要的记帐(bookkeeping)和同步,就意味着被争夺的公平锁要比不公平锁的吞吐率更低。

      非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能会造成饥饿现象。但是非公平锁相对公平锁来说极大的提高了吞吐量。

      内置的监控器锁是公平的吗?是不公平的,而且永远都是不公平的。但是没有人抱怨过线程饥渴,因为 JVM 保证了所有线程最终都会得到它们所等候的锁。确保统计上的公平性,对多数情况来说,这就已经足够了,而这花费的成本则要比绝对的公平保证的低得多。

      对于Java ReentrantLock而言,默认是非公平锁,但通过构造函数指定该锁是否是公平锁。非公平锁的优点在于吞吐量比公平锁大。对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的控制线程对锁的获取, 所以并没有任何办法使其变成公平锁。

      从代码中可以看出,公平锁与非公平锁的区别仅在于是否判断当前节点是否存在前驱节点!hasQueuedPredecessors() &&,由AQS可知,如果当前线程获取锁失败就会被加入到AQS同步队列中,那么,如果同步队列中的节点存在前驱节点,也就表明存在线程比当前节点线程更早的获取锁,故只有等待前面的线程释放锁后才能获取锁。

    独享锁和共享锁 

      独占锁获取

    • 调用入口方法acquire(arg)
    • 调用模版方法tryAcquire(arg)尝试获取锁,若成功则返回,若失败则走下一步
    • 将当前线程构造成一个Node节点,并利用CAS将其加入到同步队列到尾部,然后该节点对应到线程进入自旋状态
    • 自旋时,首先判断其前驱节点释放为头节点&是否成功获取同步状态,两个条件都成立,则将当前线程的节点设置为头节点,如果不是,则利用LockSupport.park(this)将当前线程挂起 ,等待被前驱节点唤醒

      独占锁释放

    • 调用入口方法release(arg)
    • 调用模版方法tryRelease(arg)释放同步状态
    • 获取当前节点的下一个节点
    • 利用LockSupport.unpark(currentNode.next.thread)唤醒后继节点(接获取的第四步)

      共享锁获取

    • 调用acquireShared(arg)入口方法
    • 进入tryAcquireShared(arg)模版方法获取同步状态,如果返返回值>=0,则说明同步状态(state)有剩余,获取锁成功直接返回
    • 如果tryAcquireShared(arg)返回值<0,说明获取同步状态失败,向队列尾部添加一个共享类型的Node节点,随即该节点进入自旋状态
    • 自旋时,首先检查前驱节点释放为头节点&tryAcquireShared()是否>=0(即成功获取同步状态)
    • 如果是,则说明当前节点可执行,同时把当前节点设置为头节点,并且唤醒所有后继节点
    • 如果否,则利用LockSupport.unpark(this)挂起当前线程,等待被前驱节点唤醒

      共享锁释放

    • 调用releaseShared(arg)模版方法释放同步状态
    • 如果释放成,则遍历整个队列,利用LockSupport.unpark(nextNode.thread)唤醒所有后继节点

      独占锁和共享锁在实现上的区别

    • 独占锁的同步状态值为1,即同一时刻只能有一个线程成功获取同步状态
    • 共享锁的同步状态>1,取值由上层同步组件确定
    • 独占锁队列中头节点运行完成后释放它的直接后继节点
    • 共享锁队列中头节点运行完成后释放它后面的所有节点
    • 共享锁中会出现多个线程(即同步队列中的节点)同时成功获取同步状态的情况

    重入锁 

      可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

      可重入锁的原理是在锁内部维护了一个线程标示,标示该锁目前被那个线程占用,然后关联一个计数器,一开始计数器值为0,说明该锁没有被任何线程占用,当一个线程获取了该锁,计数器会变成1,其他线程在获取该锁时候发现锁的所有者不是自己所以被阻塞,但是当获取该锁的线程再次获取锁时候发现锁拥有者是自己会把计数器值+1, 当释放锁后计数器会-1,当计数器为0时候,锁里面的线程标示重置为null,这时候阻塞的线程会获取被唤醒来获取该锁.

      重入锁的最主要逻辑就锁判断上次获取锁的线程是否为当前线程。 

    互斥锁和读写锁

      其实上面提到的独享锁和共享锁只是广义上的概念。具体的变现就是现在要说的互斥锁和读写锁。

      互斥锁:在Java中的具体表现就是ReenTrantLock,Synchronized

      读写锁:在Java中的具体表现就是ReadWriteLock。

    自旋锁

      自旋锁是指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁。好处是减少上下文切换,缺点是一直占用CPU资源。

    悲观锁和乐观锁

       乐观锁/悲观锁不是指具体类型的锁,而是看待并发的角度。  

      悲观锁:先假设别人也会对数据就行修改,所以先获得锁再进行操作。一个线程在获得锁之后其他线程只能等待。悲观锁认为存在很多并发更新操作,采取加锁操作,如果不加锁一定会有问题,synchronize就是悲观锁

       乐观锁:先假设没有其他线程会修改这个数据,不加锁,如果自己有修改操作,在提交修改的数据之前会先比较:主存中现在此数据的值a,自己所保存的此数据的copy副本b,新值c,如果a和b的值一样,说明没有其他线程修改此数据,可以正常提交;如果a和b的值不一样,说明有其他线程修改了此数据,禁止提交重新执行程序。lock就是乐观锁。乐观锁认为不存在很多的并发更新操作,不需要加锁。数据库中乐观锁的实现一般采用版本号,Java中可使用CAS实现乐观锁。

    分段锁

      分段锁:分段锁其实是一种锁的设计并不是一种类型的锁。

    偏向锁,轻量级锁,重量级锁

      1.偏向锁是为了避免某个线程反复获得/释放同一把锁时的性能消耗,如果仍然是同个线程去获得这个锁,尝试偏向锁时会直接进入同步块,不需要再次获得锁。

      2.而轻量级锁和自旋锁都是为了避免直接调用操作系统层面的互斥操作,因为挂起线程是一个很耗资源的操作。
    为了尽量避免使用重量级锁(操作系统层面的互斥),首先会尝试轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。
      3.可见偏向锁,轻量级锁,自旋锁都是乐观锁。

    锁升级

    Synchronized锁的基本介绍

    Lock锁的节本介绍

  • 相关阅读:
    VS2013折叠代码、打开代码的快捷键
    用if做了一个简单的猜拳游戏(做的不好还请指点,谢谢!)
    一维数组的应用
    do while 与while的区别!
    作业.把c语言输出的基础差不多都概括了!
    C语言基础
    c语言:蜗牛的爬行。
    QQ群成员提取
    入门教程2
    VMware WorkStations最小化安装&配置&卸载CentOS 7
  • 原文地址:https://www.cnblogs.com/htyj/p/8548741.html
Copyright © 2011-2022 走看看