• 源码分析-ReentratLock,AQS原理


    先画个大致的假类图

    主要的类都在这里,核心就是ReentrantLock的内部类 Sync,

    FairSync NonfairSync 是Sync的公平锁 非公平锁的实现

    Sync继承于AbstractQueueSynchronizer(AQS)  核心功能也都在这 先来分析AQS

    AQS的核心思想就是:  一个队列(实质是一个链表结构)  + 一个状态  /  a Queue and a state 

    大致思路 : 

    state代表当前锁的状态 默认为0,当该锁被某个线程持有则state 为1,如果是重入锁 每重入一次state+1

    当线程尝试获取锁失败就将线程自己封装成Node节点加入到队列,然后挂起等待被唤醒,再去竞争锁

    完结 !

    源码分析:

    竞争锁的队列该有的样子

    AQS的几个核心属性  : head, tail, state, exclusiveOwnerThread

    head 表示队列的头节点   (头节点不属于阻塞队列!头节点不代表实际的线程)

    state 表示当前锁的状态  0 代表无人占有  1 代表被线程占用,每+1 代表被重入一次

    tail 表示队列的尾节点

     AQS继承于AbstractOwnableSynchronizer

    继承了重要属性 exclusiveOwnerThread   表示当前占有锁的线程

    AQS内部类 Node

     几个重要的属性:

    next 后继节点,

    perv 先驱节点,

    thread 该节点代表的线程,

    waitStatus 表示后继节点是否需要唤醒 (由后继节点控制,默认是0 不需要唤醒,当后继节点需要被前置节点唤醒则将0设置为-1,如果前置节点主动放弃竞争锁 则将waitStatus > 0)

    EXCLUSIVE 标识节点会独占锁    (目前看来用在ReentratLock上抢锁)

    SHARED 标识节点会共享锁  (目前看用在CountDownLautch抢锁)

    从客户端调用入手分析

    加锁过程

    public static void main(String[] args) {

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
    //do something
    }finally {
    lock.unlock();
    }

    }

    1.初始化  无参默认是非公平锁

    2.lock.lock();  非公平锁的实现

     先试探CAS去改锁的状态,如果成功则加锁成功! 将持有锁的线程设置为自己 over!

    如果CAS失败则 执行acquire(1), 参数传1

    3. acquire  尝试获取锁失败,并且加入到阻塞队列还被中断了,自己重新标记一下中断位

    4.  重点:tryAcquire(1)  尝试去获取锁  传的参数是1

     非公平锁的tryAcquire()实现,

    先判断state是否为0 (当前锁是否没被持有)

    if (state == 0){

      if (尝试CAS直接去改变状态 因为此时可能会有多个线程同时发现了state是0 都想去改变状态){

        如果CAS成功了就将持有锁的线程设置为自己 over!

      }

    }else if (当前线程就是当前持有锁的线程,说明这是锁重入 ){

      直接state+1

    }

      尝试获取锁失败了 return false;

    5.  boolean acquireQueued()   中断当前线程是否成功 , 传入封装好的节点  和参数1

     try {

      for(;;){

        获取当前节点的前直接点 p

        if (如果p是head节点 并且 再次尝试获取锁成功了){

          就把当前节点设置为head节点

           下个节点置空

           return  中断当前线程失败

        }

        if ( 再次抢占锁失败后是否需要挂起 && 检查并挂起线程){

          中断成功

        }

      }

    } finally {

      //failed == true的情况  再次尝试获取锁抛异常导致

      if (如果中断失败了){
        解散节点 不玩了 

      }

    6. addWaiter()  尝试加入到阻塞队列   传入的mode标识当前节点是想 独占锁 还是想共享锁

    将当前线程封装成一个Node

    获取到尾节点 pred

    if (如果尾节点不为null){

      将perd作为当前节点的前置节点

      if (CAS将当前节点设置为队列的尾节点){

        return 当前节点;

      }

    }

    说明尾节点是null,就说明没有head节点,

    for循环{

    判断尾节点是否为空,如果为空则CAS 将自己设置为head节点 兼 尾节点,

    如果中途判断发现尾节点不为空了(可能是被别人抢先占了头节点了),就默默的加入到尾节点后面

    }  

    解锁过程 unlock()

    1. 

     unlock() 是 Sync的方法

    2. release()  传入参数1   

     if (尝试去释放锁成功) {

      获取头节点

      if (头节点不为null &&  头节点的下一个节点需要被唤醒) {

        唤醒

      }

    }

    tryRelease()  传入1,很简单  尝试释放锁  

    修改state - 1

    如果state减为0  自由了 将持有锁的线程置空

    如果是state降为0 return释放锁成功,否则重入锁-1 return false

    3.  唤醒节点   传入头节点

    if (头节点的下一个节点需要被唤醒) {

     CAS修改waitstatus 为0  表示不需要再换醒了

    }

    获取头节点的下一个节点 s

    if (下一个节点是空,或者下一个节点已经退出竞争锁了) {

      将节点s 置空

      for () {

        for循环再去找 直到找到下一个节点符合的

      }

    }

    最终 s 不为空 就将s的线程唤醒

     最终通过unpark 唤醒线程

    公平锁与非公平锁区别

    1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
    2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

    公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

    相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

  • 相关阅读:
    select详解
    java Map及Map.Entry详解
    Java 基本类型
    java 获取String出现最多次数的字段
    java 居民身份证的校验
    java 删除文件
    Java 导出excel进行换行
    获取文件及其文件路径
    List<Map<String,Object>> 中文排序
    Java ----单个list 删除元素
  • 原文地址:https://www.cnblogs.com/ttaall/p/13828134.html
Copyright © 2020-2023  润新知