ReentrantLock 如何实现非公平锁?和公平锁实现有什么区别

reentrant 英[riːˈɛntrənt] 美[ˌriˈɛntrənt] 先学会读。单词原意是可重入的html

  1. 考察显示锁的使用。可延伸知识点
    1. 独占锁 & 共享锁
      1. 独占锁 - 悲观锁(不能同时被多个线程持有 - synchronized锁 & ReentrantLock)
      2. 共享锁 - 乐观锁(ReentrantReadLock )
      3. 读共享、写排他
    2. 重入锁  
      1. 方法进行深层次调用时,获取同一把锁可以获取到,不会死锁  
    3. 使用范式
      1. finally 
        复制代码
                Lock lock = new ReentrantLock();
                lock.lock();
                try{
                    //一顿操做
                }finally {
                    lock.unlock();
                }    
        复制代码
    4. CAS原理实现乐观锁
      1. 原理 : 在线程对数据进行修改时,须要对比如今持有的变量和原始地址中的值是否相同,若相同则替换成功,若不一样替换失败
      2. 实现乐观锁 = 自旋 + CAS
      3. 问题 :
        1. ABA问题 - jdk AtomicStampedReference  AtomicMarkableReference
        2. 自旋时间过长会致使CPU消耗过大
        3. 一次操做只能修改一个内存地址的变量 AtomicReference<V>
      4. jdk相关实现类
        1. AtomicInteger  
        2. AtomicReference<V>  多个共享变量一块儿操做
        3. AtomicReferenceArray  操做时是复制了原数组一份,修改后原数组的值不变
        4. AtomicMarkableReference 解决ABA问题,但只关心是否改变
        5. AtomicStampedReference 解决ABA问题,会记录改变次数.可经过getStamp()获取
    5. condition - wait&notify --> ***
    6. CHL队列锁 - 基于链表
      1. 每一个线程拿锁时建立一个Node,locked状态置为true,把本身放到链表的tail,而后把myPred指向以前的tail,以后向前循环check locked状态,直到为false时本身就拿到了锁
    7. AQS - 抽象队列同步器
      1. CHL实现的变种 -- 双向链表
      2. 实现多线程访问共享资源的同步框架FIFO
      3. 采用模板方法,一些方法须要继承者实现,但为何不设计成abstract方法(抽象方法都要实现,为开发者考虑,独占式获取锁只实现独占方法,共享方式只实现共享方法)
        1. 独占式 tryAcquire、 tryRelease、isHeldExclusively
        2. 共享 tryAcquireSharedtryReleaseSharedisHeldExclusively
        3. 方法都有一个参数 ?
      4. state属性
        1. volatile int state 表明共享资源
        2. 提供三种访问方式 getState()、setState()、compareAndSetState() 
          1. setState 和 compareAndSetState 有什么区别 ? 前者不是有安全问题吗,为何还存在。(setState 是在已经拿到锁的状况下调用,不会有安全问题)
        3. ReentrantLock 用来标记拿锁的次数、CountDownLatch 用来标记任务的个数
      5. acquire()方法
        1. tryAcquire()
        2. addWaiter() 默认独占式 - 自旋添加节点
        3. acquireQueued() 真正的拿锁方法 返回等待过程当中是否被中断过,自旋+阻塞获取资源 
          1. 若前驱结点是head且拿到了锁的状况下,把当前节点置为head节点,并把原head节点脱离
          2. shouldParkAfterFailedAcquire(Node, Node) 返回前驱节点是否处于等待状态Node.SIGNAL。并将本身放在此节点的后置节点
          3. parkAndCheckInterrupt() 返回是否被中断过。阻塞当前线程,若是线程被唤醒,检查是被打断仍是被正常唤醒
        4. 先去尝试拿锁,拿不到就将本身放在等待队列尾部,而后自旋向前寻找,直到head节点拿到锁为止。即便中间被打算,等待过程也不会中断。而是在拿到锁以后再中断本身
      6. release()方法
        1. tryRelease(arg)
        2. 释放锁成功以后调用 unparkSuccessor(head) -> 将head节点状态置为0,用unpark()唤醒等待队列中最后边的那个未放弃线程
        3. 唤醒acquireQueued方法中的判断,让线程拿到锁
      7. acquireShared()方法
        1. 相似acquire()  
        2. 多一个setHeadAndPropagate()方法。在拿到资源的时候向后唤醒 - 体现共享
        3. 调用了doReleaseShared()
      8. releaseShared()
        1.  doReleaseShared()释放掉资源后,唤醒后继  实际调用unparkSuccessor(head)
    8. 公平锁&非公平锁
      1. 遵循拿锁的顺序
    9. 隐式锁synchronized 对比
      1. lock提供一些除lock()操做以外功能,更加灵活
      2. 非特殊状况下使用synchronized,jdk对它的优化比较大
    10. 模板方法
  2. 实现
    1. ReentrantLock自身并未继承AQS,而是采用内部类Sync继承。屏蔽内部实现、外部调用者不用关心具体细节
    2. 如何实现可重入
      1. tryAcquire if(state ==0 )的else中进行当前线程锁的累加
    3. 公平锁和非公平锁有什么区别
      1. 非公平锁再获取锁的时候不按排队顺序而是随机拿锁 
      2. tryAcquire方法中!hasQueuedPredecessors() 来断定队列中是否有前驱节点在等待锁
    4. 在阻塞以前,线程都会经过shouldParkAfterFailedAcquire去修改其前驱节点的waitStatus=-1。这是为何?为了release时unparkSuccessor(head) 唤醒后续节点
    5. unparkSuccessor时为何会出现s==null || s.waitStatus>0的状况,这种状况下,为何要经过prev指针反向查找Successor节点?
      1. s == null 是由于acquireQueued() 在拿到锁以后会将head.next = null .这样链表就断了,因此要从尾部节点向前找
      2. s > 0 是在cancel的时候,节点在head节点的后继节点断开。致使链表断裂。  

总结: 如何回答这个问题。数组

1. ReentrantLock 经过定义一个内部类来实现Lock接口,公平锁和非公平锁的实现都是基于这个继承自AQS的内部类Sync。不一样的点是在拿锁的时候,非公平锁会直接拿锁,而公平锁会判断队列中是否还有线程在等待锁。若是有就会拿锁失败。安全

2.后面就是回答AQS原理了多线程

 

参考AQS http://www.javashuo.com/article/p-xcevmtwv-gz.html    框架

图解 https://www.jianshu.com/p/b6efbdbdc6fa优化

相关文章
相关标签/搜索