美文网首页
并发--AQS

并发--AQS

作者: 简书徐小耳 | 来源:发表于2019-04-01 17:05 被阅读0次

    AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer的区别

    • 1.前者state采用int,后置采用long。
    • 2.可以更方便的使用state的高位进行一些额外的操作
    • 3.也可以包含更多的锁获取(比如读锁目前支持的是int的最大值)

    类的结构分析

    Node类--CLH队列里面的元素类型

    SHARED和EXCLUSIVE

    • 分别代表是共享锁和独占锁。

    waitStatus

    • 1.CANCELLED(1)
      当前节点中断或者超时
    • 2.SIGNAL(-1)
      该节点的后续节点一定或者很快是阻塞的,
      所以当前节点释放或者取消的时候会去唤醒后续节点
      为了避免竞争,获取锁的方法必须先暗示他们需要一个signal类型的节点,
      以原子方式重试获取。
    • 3.CONDITION(-2)
      在condition队列的节点
    • 4.PROPAGATE(-3)

    prev和next

    • 该节点的前后节点。

    thread

    • 这个节点对应的线程。

    nextWaiter

    • 如果是condition队列则是指向队列下一个,还可以通过该值判断是否是shared。
    • 因为condition队列都是exclusive mode

    ReentrantLock(独占锁)

    公平lock和非公平lock

    • 1.非公平锁会先使用CAS获取下锁,如果获取成功直接返回,否则按照公平模式去进行。
    • 2.公平lock的获取锁的方式是阻塞的不可中断,但是会在获取锁成功之后检测到如果中断过会进行自我中断。
    • 3.首先调用tryAcquire,如果失败则调用addWaiter和acquireQueued,最后获取到锁并判断期间是否中断过,来执行selfInterrupt
    • 4.公平的tryAcquire的逻辑:如果锁无人获取,则再去判断是否有同步队列,如果没有则尝试获取锁,获取失败则进入同步队列。如果锁已经被获取了,则查看获取线程和已获取线程之间是否是同一个是则可以重入,否则进入同步队列。
    • 5.非公平的tryAcquire的逻辑:如果锁无人获取,则直接cas获取。获取失败则进入同步队列,如果锁已经有人获取了,判断获取锁和已获取锁的线程是否一致,一致则重入,否则进入同步队列。
    • 6.addWaiter:将线程和Exclusive包装成Node,首先尝试直接插入tail,失败则通过自旋+CAS插入尾部。
    • 7.acquireQueued:首先判断当前节点的前置节点是否是head(head的节点代表已经获取锁,即将释放锁,或者是初始的空节点),是head则调用tryAcquire去获取锁,获取成功置换head。不成功则检测前置节点的状态如果是Signal则代表该前置节点后期会被
      其前置节点唤醒,如果ws大于0代表该前置节点已经被cancel则将该节点从链表中剔除,如果ws==0或者为progragate则将其置为Signal,等待后期被唤醒去获取锁。
      只要前置节点是Signal 就让他去Park.

    公平lockInterruptibly和非公平lockInterruptibly

    • 1.首先检测是否已经中断了,如果是则抛出异常
    • 2.然后调用tryAcquire,如果失败则调用doAcquireInterruptibly,该方法是addWaiter和acquireQueued的集合,不同之处在于
      当线程被唤醒之后如果发现已经中断了则直接抛出异常。

    非公平的tryLock和公平和非公平tryLock(timeOut)

    • 1.tryLock只有非公平的,只有当前锁没有被获取或者已经获取但是可以重入的情况才行,否则直接返回
    • 2.公平和非公平tryLock(timeOut)支持中断,先调用tryAcquire,如果失败了再调用doAcquireNanos,doAcquireNanos方法的逻辑:
      其逻辑和addWaiter和acquireQueued相似,只是增加了对timeout的检测,如果再规定时间没有得到锁则返回,同时也会抛出中断异常。

    不分公平和非公的unlock

    • 1.主要是调用tryRelease和unparkSuccessor
    • 2.tryRelease的逻辑:先去检测当前线程是否有持有独占锁,不独占则抛出异常。当检测到释放后的state是否为0,如果是则解除独占状态,否则只是修改state。
    • 3.如果state==0则去同步队列中从head节点的下一个开始寻找可以唤醒的节点,具体的逻辑在unparkSuccessor。
    • 4.unparkSuccessor逻辑:先清除当前head节点的waitstatus=0,然后看当前节点的下一个是否为null或者为取消状态,如果是则从同步队列尾部寻找最后一个节点是未取消的,然后唤醒他。
    • 5.从同步队列尾部获取的逻辑:如果我们从前往后寻找未取消的节点,容易遗漏最后一个节点(因为节点进入同步队列的时候是先设置tail,再设置next),而从tail尾部开始则不会遗漏。

    ReentrantReadWriteLock(独占和共享)

    readLock的lock(共享)

    • 1.lock主要是调用tryAcquireShared,doAcquireShared。
    • 2.公平tryAcquireShared的逻辑:首先判断是否已经被获取写锁了,如果是则查看是否是当前线程,不是的话直接返回,是的话就重入。
      如果没有写锁,判断目前是否已经有同步队列,有的话直接进入fullTryAcquireShared。没有的话则判断共享锁的数量是否已经达到最大值了,有的话则进入fullTryAcquireShared。否则
      CAS获取锁,获取成功之后就返回,获取失败就进入fullTryAcquireShared。
    • 3.非公平的tryAcquireShared:非公平的模式与公平模式的区别在于通过apparentlyFirstQueuedIsExclusive判断等待队列的第一个(head.next)是否是写锁,如果是则当前节点需要进入等待队列,这是为了
      防止写饥饿。一般进入队列都是写锁或者写锁被获取则其他读锁进入排队。
    • 4.fullTryAcquireShared的逻辑:自旋的去获取锁,获取失败则进入doAcquireShared。可以看到只有读线程只有在锁达到上限或者写锁被获取的情况下才进入同步队列,其他情况下都是自旋获取锁
    • 5.doAcquireShared:和addWaiter和acquireQueued功能相似,支持获取锁之后中断,其中涉及到setHeadAndPropagate方法
    • 6.setHeadAndPropagate:因为是共享的,如果head获取到了锁且还有资源则还会去唤醒后继结点,毕竟是共享模式!,里面用到了doReleaseShared
    • 7.doReleaseShared的逻辑:因为head的next是shared模式所以我们需要先判断head节点是否为signal,是的话才可以唤醒,不是的话如果是0则置为PROPAGATE,如果head节点没有变化则代表操作完成直接返回。

    readLock的其他lock大致相似。

    读锁为了也支持重入,对于对一个获取读锁的线程会进行记录,并且实时更新重入次数,其他读锁线程是根据threadlocal计数,第一个线程如果释放了,后续其他线程会作为第一线程替代。

    ReentrantLock是直接使用state的十进制值。而读写锁则是使用高位和低位表示写锁的数量。

    readLock的unlock

    • 1.主要是由tryReleaseShared和doReleaseShared组成.
    • 2.一般情况下释放没问题,其主要是释放重入和读锁的计次,同时如果发现读锁都已经释放完了则调用doReleaseShared,准备去唤醒同步队列里面第一个节点,这个节点基本上就是写线程,因为在读锁释放的时候,写锁肯定进入等待队列
      其他读锁都在循环获取锁。

    writeLock的lock

    • 1.其和ReentrantLock的lock大致逻辑相似。主要就是tryAcquire,acquireQueued,addWaiter
    • 2.tryAcquire的逻辑是:当有锁被获取了 看看是否是读锁,是就返回。不是的话看看写锁是否是自己获取的,不是就返回。
      ,然后代表可以获取写锁,如果写锁重入数量未达到上限即可以获取,反之则返回。如果当前未有锁被获取,则公平锁会去查看是否有等待队列,
      非公平则是直接尝试cas获取锁。如果获取锁失败则调用addWaiter,然后进入acquireQueued。

    writeLock的unLock

    • 其和ReentrantLock的unlock相似

    读写锁升级和降级的问题。

    • 1.锁降级:从写锁变成读锁;锁升级:从读锁变成写锁
    • 2.很显然当一个线程获取读锁之后无法再获取写锁,如果可以的话,其他已经获取读锁的线程无法感知到写锁作出的改变。
    • 3.从代码角度看获取写锁的条件就是无读锁,无其他线程写锁才可以。
    • 4.支持写锁变为读锁,从代码角度很容易实现,因为获取写锁之后直接变为读锁,不需要重新竞争读锁,同时写锁修改的值只要写锁unlock那么也可以让其他线程该知道。而对于本线程而言不需要写锁unlock就可以
      感知到改变的值。

    condition

    只有独占锁,reentrantLock和reentrantReadWriteLock.writeLock才有condition

    condition只提供了await和signal相关方法,类似于Object类的wait和notify

    • 1.其比object类的好处是提供了多种条件等待方式,比如我们一把锁 三个地方都在争取,条件不同,我们只唤醒满足条件的等待线程。

    condition的await机制

    • 1.将当前线程包装成节点加入条件队列,然后释放锁。
    • 2.循环判断当前节点是否已经从条件队列移动到同步队列,如果没有则沉睡,可中断。
    • 3.然后阻塞式获取锁,在之后清除等待队列已经cancel的节点,然后最终根据整个过程是否中断过来决定是否抛出异常。

    condition的signal机制

    • 1.唤醒第一个等待队列节点并转移到同步队列中

    volatile对AQS的影响

    • 1.首先再获取锁的代码块前后都是先获取state,再设置state。
    • 2.2个volatile在获取锁的代码块首尾插入内存屏障禁止获取锁的代码块和前后代码重排序,但是获取锁的代码可以进行优化重排序。
    • 3.通过内存屏障可以保证获取到state的最新值,当某个线程获取到锁之后则被锁着的代码块,只会被单线程执行,如果过程中
      涉及到对变量的修改,其会在释放锁之前因为volatile的内存屏障导致,修改的值可以被其他线程发现。

    原子

    • 1.其通过底层的Park和volatile结合,让同一时间只有一个线程执行,保证了原子性

    有序

    • 1.其通过volatile的内存屏障,禁止了获取锁的代码块和临界区外的代码块进行重排序保证了有序性

    可见性

    • 1.通过内存屏障,保证了获取锁之后的修改以及对state的修改会被其他线程立即发现。

    相关文章

      网友评论

          本文标题:并发--AQS

          本文链接:https://www.haomeiwen.com/subject/jozvbqtx.html