zoukankan      html  css  js  c++  java
  • AbstractQueuedSynchronizer

    AQS, 即AbstractQueuedSynchronizer,一个基于FIFO的队列同步器,是实现lock的基础,AQS是一个抽象类,继承了AbstractOwnableSynchronizer抽象类,其总体结构如下:

    包含内部类Node ,ConditionObject。

    AQS的结构

     AQS是一个基于双向链表的队列,类中定义了三个变量head, tail, state  分别代表队列的头结点,尾节点,对应锁状态(是否被占用、被重用),当一个线程获取同步锁失败时,会将该线程信息封装成一个Node对象存放在AQS队列的尾部,并阻塞该线程,当同步状态释放时,会去唤醒队列的头结点。

    Node类

    关于node类,保存的是获取同步锁失败的线程信息,其中最关键的是四种(五种)状态:

    Cancelled : 取消状态,值为1,表示当前线程状态为取消状态,线程被标记为取消状态后,后续操作会将该node节点从队列中删除。

    Signal :  唤醒状态, 值为-1,  表示当前线程状态为唤醒状态,线程被标记为唤醒状态后,后续操作会让node节点对应线程会去尝试获取同步锁。

    Condition: condition(条件)状态,值为-2, 表示当前线程等待一个Condition条件唤醒

    Propagate:(没看明白)

    初始状态:值为0

    ConditionObject类:


    稍后补充

    AQS源码

    看AQS源码源码,可以结合ReentrantLock源码一起查看,下图是ReentrantLock,FairSync,NonefairSync,ReentrantLock之间关于获取锁的流程图(红色表示非公平锁获取的锁的主要步骤,蓝色表示公平锁获取锁的主要步骤,黑色表示都要执行的步骤:

    锁的获取

    非公平锁(NonefairSync):

    1.生成锁对象

     

    默认生成非公平锁, 调用lock()方法时执行的操作

    1.首先就去尝试获取同步状态锁(CAS操作,CompareAndSet),如果能获取锁,则将同步状态锁的独占状态线程为当前线程;
    
    2.如果没有获取成功,则执行acquire(1)方法

    此处是公平锁与非公平锁第一处区别较大的地方,非公平锁获取锁时会直接去设置锁的对应线程为当前线程而非先放入队列中依次获取。

    2.获取锁

    1.非公平锁对象首先回去调用tryAcquire(arg)方法,如果获取到锁(返回true),执行完毕;
    
    2.如果获取锁失败,执行assWaiter(Node.EXCLUSIVE)方法,将当前线程信息封装成一个node对象;
    
    3.调用acquireQueued(node, arg)方法将封装好的node对象放入AQS队列中

     

    首先查看tryAcquire(arg)方法

    tryAcquire(int acquires)方法直接调用的是RenntrantLock内部抽象类中的非公平获取锁的方法(nonfairTryAcquire(int acquires))

    1.获取同步锁的状态,如果状态值为0(表示没有现成占用),则将同步锁的状态值设置为1,并将拥有同步锁的线程设置为当前线程,返回true; 
    
    2.状态值不为0(表示锁已被占用),则去判断占用锁的线程是否是当前线程,如果是锁的状态值加1(可重入锁的设计),返回true;
    
    3.如果所有条件都不满足(获取锁失败),返回false

     3.封装线程,将线程信息封装为node节点对象

    1.首先会去判断AQS队列中tail节点是否为null;
    
    2.如果tail节点存在,会将node节点放到队列的尾部并设置为tail节点;
    
    3.如果tail节点不存在执行enq(node)方法;
    
    4.返回node节点

    关于enq方法的逻辑 ,通过此方法必然会初始化AQS队列

    1.如果tail节点不存在,则会去调用enq(node)方法,通过自旋的方式先创建一个空的节点作为head节点、tail节点;
    
    2.类似于addWaiter方法中的操作,将node节点添加到队列的尾部

    4.将线程信息node节点放到队列中,该方法也是通过自旋的方式完成的

    1.首先获取node节点的前驱结点;
    
    2.如果前驱节点是node节点并且当前线程尝试获取锁成功,则将当前node节点设置为head节点并将拥有锁的线程设置为当前线程;
    
    3.如果node的前驱结点不是head节点或者获取锁失败,则去判断node节点对应线程是否应该被挂起;
    
    4.3成立的前提下执行挂起操作

    先看下shouldParkAfterFailedAcquire(p, node)方法,该方法主要是根据线程的状态值来判断

    1.如果线程的状态值为-1(即SINGLE(唤醒)状态),直接返回true;
    
    2.如果是大于0(即1, CANCLLED(取消)状态),通过循环的方式将取消状态的node节点从AQS队列中删除;
    
    3.如果是-2(CONDITION(等待条件)状态)或者PROPAGATE状态,都将node状态设置成SINGLE状态。

    如果判断线程是否应该被挂起返回值为true(即SINGLE状态),则将当前线程通过LockSupport.park(this)方法将当前线程挂起,并将interrupted设置为true。

    以上就是非公平锁的获取方式。

    公平锁(FaireSync)

    1.生成公平锁对象(true)

    2.获取锁

    公平锁与非公平锁关于锁的获取的区别主要有两点:

    a.公平锁在获取锁时会直接调用acquire(1)方法获取锁,非公平锁获取先尝试获取锁,如果尝试获取失败在调用acquire(1)方法。

     

    b.在进入tryAcquire(int acquires)方法中,公平锁相对于非公平锁来说多了一个hasQueuedPredecessors()的判断方法,该方法的作用是判断是否有其他等待锁的线程比当前线程等待时间更长,如果有则当前线程获取锁失败,这里则体现了公平的概念(先到先得,依次排队)

    3.剩余步骤中获取锁的方法,公平锁与非公平锁是一模一样的。

    锁的释放:

    公平锁与非公平锁关于锁的释放,步骤其实是一样的

    1.调用AbstractQueuedSynchorinzed的release(int arg)方法

     

    release(int arg)方法会去调用ReentrantLock中Syncl内部类中的实现

    1.首先获取锁的状态值(是否被占用,是否被重入)并减1;
    
    2.如果当前释放锁的线程不是拥有锁的线程,则抛出异常;
    
    3.如果状态值为0(即锁被释放掉),则设置拥有所得线程为空,设置释放成功的标识;
    
    4.设置状态值,返回标识

    1.如果所释放成功(即状态值为0,返回值为true),会去判断当前AQS队列中head节点(即当前线程对应的node节点)是否为空以及head节点对应线程的状态是否是初始状态;
    
    2.如果不为空且不为初始状态,则会先去将head的状态设置为初始状态;

    3.如果head节点的next(后继节点)为空或者状态值大于0(取消(CANCLLED)状态),从tail节点遍历,一直到前驱节点为空的节点为止;

    4.如果head节点的next(后继节点)不为空,唤醒head节点的后继节点

     

  • 相关阅读:
    Java面向对象知识点总结
    JAVA编程必学必会单词集(1)
    Linux 帮助命令
    学习笔记
    day4
    复习
    day5
    day04
    day3
    day02
  • 原文地址:https://www.cnblogs.com/lenmom/p/12018293.html
Copyright © 2011-2022 走看看