• ReentrantLock原理(未完待续)


    一. 概述

    ReentrantLock底层基于AQS,其构造方法返回的就是NonfaireSync和faireSync;
    两种同步器都继承自Sync,Sync又继承自AQS!在这里插入图片描述

    在这里插入图片描述
    new ReentrantLock时,返回的就是同步器,默认是非公平的;

    二. 原理分析:

    1. 非公平锁的加锁

    1. 1 加锁成功

    调用lock() 方法,使用CAS机制尝试将AQS底层 volatile修饰的 state 属性改为1,并将当前线程设为exclusiveOwnerThread即占有锁;
    若失败即出现竞争,则进入acquire() 方法;
    在这里插入图片描述

    1. 2 加锁失败

    1. 当锁已经被占用,共享内存中的 state属性是1,compareAndSet(0,1) 会失败,CAS更改state属性失败,会进入 acquire() 方法;
    2. 进入else中 AQS的 acquire() 方法;
    3. 调用 tryAcquire() 再次尝试,如果还失败,再执行acquireQueued() ,如果是第一次会创造一个哨兵节点,然后再创造一个Node节点对象关联线程,添加到FIFO等待队列(双向链表)中;
    4. 节点进入队列后,会循环尝试加锁,失败后会被park阻塞,节点前驱节点的waitStatus 会被置为 -1,-1表示可以去唤醒后继节点;

    AQS中的acquire()方法:
    在这里插入图片描述
    假设多个线程多竞争失败,进入了FIFO等待队列:
    在这里插入图片描述

    1. 非公平锁的释放

    1.1 unlock() 释放锁+唤醒线程

    1. 调用 unlock() 方法,底层调用了release() 方法,使用tryRelease()state置为0,并将exclusiveOwnerThread置为null,即释放锁;
    2. tryRelease() 返回true后,判断head哨兵节点是否为null ,如果否 且哨兵节点的waitStatus 也不会0(为-1),则哨兵节点的后继节点被unParkSuccessor 唤醒 !线程就恢复允许了,就有机会去竞争锁;原来的节点就从队列中断开;

    在这里插入图片描述

    在这里插入图片描述

    1.1 竞争失败

    如果此时又来了一个新的线程,那么就会和队列中要被释放的线程一起竞争锁,
    如果新的线程获取到了锁成为了exclusiveOwnerThread,则队列中的线程解锁失败,
    在for中循环tryAcquire返回false,又被阻塞住;
    在这里插入图片描述

    2. 可重入锁

    2.1 加锁(state自增)

    加锁最终调用nonFairTryAcquire( 1 ), 先获取state值,
    如果state=0即无锁,并使用CAS将state改为1,并将当前线程设为exclusiveOwnerThread,即占有锁;
    如果state=1,则判断当前线程是否等于exclusiveOwnerThread,如果是即锁重入,就让state值++;
    在这里插入图片描述

    2.2 解锁(state自减)

    调用tryRelease(1)方法,使state减去参数中的1,
    如果减1之后state为0,则将exclusiveOwnerThread设为null,即释放锁,并返回true;
    如果state减1之后不为0,则返回fasle;
    在这里插入图片描述

    3. 可中断机制(跳出阻塞)

    【不中打断的模式】下,即使它被打断,会仍然驻留在AQS的等待队列中被阻塞;
    【可打断模式】下,如果没有获得锁,进入 doAcquireInterruptibly() 方法而不是acquireQueued(),而不是在线程被park阻塞的状态时,如果被interrupt则会抛出异常,线程就不会在AQS中的等待队列继续等待;
    在这里插入图片描述
    在这里插入图片描述

    4. 公平锁(加锁时 看排队位置)

    非公平锁:
    加锁时,最终调用nonFairAcquire(),如果state=0,则直接用CAS机制将state改为1,设置Owner线程,不去检查AQS的等待队列;

    公平锁:
    1.最终调用 tryAcqure() 方法,会用hasQueuePredecessors()方法检查当前线程在AQS等待队列中的位置;
    2.hasQueuePredecessors即 检查当前这个线程在AQS队列中的位置是否处于head.next(最优先的位置,head是哨兵节点不关联线程),
    如果线程是 就使用CAS机制将state设为1,并设置当前线程为exclusiveOwnerThread,
    不是就不会往下执行,不会用当前线程占有锁;

    在这里插入图片描述

    在这里插入图片描述

    5. 条件变量

    作用类似synchronized的waitset !
    每个条件变量其实就对应着一个等待队列(双向链表),实现类是AQS中的ConditionObject;

    5.1 await()

    起初state=1,线程-0是Owner线程占用着锁,调用await()时:

    1. 进入addConditionWaiter()方法,会创建节点关联当前线程-0,并将节点加入到条件变量的队列中去,然后将节点的waitStatus状态设为 -2 ,并且执行park阻塞该线程;
      (-2在条件变量里是等待的状态);
      (如果队列为空,就将节点作为firstNode,如果不为空就加到队列的尾部);
    2. 执行fullyRelease():然后将当前这个线程占用的锁都释放掉,因为可能有锁重入;
    3. 释放锁都会将阻塞队列中head的后继节点唤醒,即unparkSuccesser(),后面的线程就能去竞争锁;

    在这里插入图片描述
    在这里插入图片描述

    5.2 singal()

    只有Owner线程才有资格唤醒条件变量中的线程;

    假设Thread-1 唤醒Thread-0:

    1. 先判断当前线程是不是锁的持有者,如果不是则报错;
    2. 获取队列头元素firstWaiter,如果不为空,就调用doSignale()将其移除等待队列,
      然后再将这个节点转移到同步器的阻塞队列中并将waitStatues改为0(因为阻塞队列最后一个节点的waitStatues是0), 这样线程就有机会抢占锁了;
      如果遇到中断等,就取消转移,将等待队列的下一个节点来唤醒;
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • 相关阅读:
    linux内核整体架构
    【MySQL】MySQL的存储过程(1)
    EasyCVR视频汇聚平台显示有视频流但无法播放是什么原因?该如何解决?
    Linux网络编程-详解http协议
    自己动手写一个Golang ORM框架
    逼格提升:内存泄漏检测工具
    猿创征文 | 常见的五款BI报表介绍
    详解(一)-ThreadPollExecutor-并发编程(Java)
    java基于ssm+vue的婚纱摄影网站
    [面试直通版]操作系统之锁、同步与通信(下)
  • 原文地址:https://blog.csdn.net/Swofford/article/details/126084022