Java AQS初步介绍

前言:


AQS作为JUC中各种同步器和锁的基础框架,如果直接看译文可能有点模棱两可,这里先给出一点AQS的基本介绍,方便后续译文的理解。

成员变量:

private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;

成员方法:

/** 独占式获取同步状态,尝试获取,成功返回true,反之为false */
protected boolean tryAcquire(int arg)
/**独占式释放同步状态,如果成功释放,等待中的其他线程有机会获取同步状态 */
protected boolean tryRelease(int arg)
/** 共享式获取同步状态,返回值小于0,获取失败,返回值等于0,共享式获取成功,但后续共享式获取失败,返回值大于0,共享式获取成功,后续共享式也会获取成功*/
 protected int tryAcquireShared(int arg) 
/**共享式释放同步状态,成功返回true,失败返回false */
protected boolean tryReleaseShared(int arg) 
/** 独占式获取同步状态,不管有没有中断*/
public final void acquire(int arg) 
/** 独占式获取同步状态,中断后放弃获取*/
public final void acquireInterruptibly(int arg)
/** 共享式获取同步状态,不管有没有中断*/
public final void acquireShared(int arg) 
/** 共享式获取同步状态,中断后放弃获取*/
public final void acquireSharedInterruptibly(int arg)
/** 共享式获取同步状态,中断后放弃获取,超时获取失败*/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
/** 独占式获取同步状态,中断后放弃获取,超时获取失败*/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
/** 独占式式释放同步状态*/
public final boolean release(int arg)
/** 共享式释放同步状态*/
public final boolean releaseShared(int arg) 

静态内部类

static final class Node {
    /** 标记该节点为共享模式下的等待 */
    static final Node SHARED = new Node();
    /** 标记该节点为独占模式下的等待 */
    static final Node EXCLUSIVE = null;
    /** 超时或中断而取消 */
    static final int CANCELLED =  1;
    /** 当前节点取消或者释放时,后继节点需要unpark*/
    static final int SIGNAL    = -1;
    /** 该节点是条件队列中节点,不能用于同步队列节点 */
    static final int CONDITION = -2;
    /**共享释放同步状态时,头节点的状态,即随后的共享获取可以无条件传播 */
    static final int PROPAGATE = -3;
    /** 同步队列默认值为0,条件队列默认值为CONDITION */
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;

正文:

AQS类提供了一个实现锁和同步器如Semaphores、CountDownLatch等组件的基础框架。AQS类主要包括acquire获取同步锁状态和release释放同步锁状态,获取同步状态的基础算法是try acquire,同样地释放同步状态的基础算法是try release,如果获取同步状态成功解除队列第一个线程的阻塞,失败则返回。所有线程在一个FIFO的等待队列(CLH)中等待,其中抽象方法tryAcquire()和tryRelease()根据子类的具体需求而实现。

1.独占式获取锁

独占模式的获取同步状态的一般流程如下:
这里写图片描述

在上图中,‘shouldParkAfterFailedAcquire?’确认前驱节点的等待状态是否为SIGNAL。如果前驱节点的状态是SIGNAL,该节点会进入阻塞状态,因为它知道前驱节点一旦释放同步状态会立刻通知该节点;如果前驱节点的状态不是SINALA,而且它是队列的第一个节点可能重试获取锁。
这里写图片描述

2.队列

如果一个线程获取锁失败,该线程会加入同步队列中。如果队列不存在,就使用dummy header初始化队列,并指向该节点。头节点的‘next’指针指向当前节点的‘prev’,新节点此时也是尾节点。头节点的等待状态设为SIGNAL,这样拥有锁的线程释放锁后,可以通知头节点的后继节点来获取锁。在线程park前,它会一直尝试获取锁状态直到确定不能获取锁。

  • 注:CLH锁即Craig,Landin,和Hagersten locks,是一个自旋锁,可以保证无饥饿性,提供先来先服务的公平性。CLH锁基于链表实现的一个可扩展、高性能、公平性的自旋锁,应用线程只在局部变量上自旋,通过不断轮询前驱的状态,如果发现前驱释放了锁,就结束自旋。CLH队列节点有一个locked布尔变量,true表示需要获得锁,false表示线程释放了锁。更多关于CLH锁的详细内容请访问原文https://www.programering.com/a/MjM5gTNwATE.html
    AQS中采用的是变种的CLH队列,每个node节点里使用一个状态字段来控制阻塞,‘prev’指针用于处理取消,‘next’指针用于实现阻塞。

这里写图片描述

因此,只要获取锁失败的线程的前驱节点的等待状态设置为SIGNAL,它就可以一直安全地挂起,如果前驱节点释放同步状态,它就可以重新获取同步状态。如果前驱节点被取消,就会跳过所有取消的前驱节点并重置next和prev指针指向一个waiting的线程。
这里写图片描述

3.独占式释放锁

这里写图片描述
根据子类的需要实现‘try Release’,一旦释放同步状态,头节点的后继节点需要收到通知,然后可以重新获取锁。如果没有头节点,也就意味着队列中没有等待的线程,因此也就不需要发出任何通知。如果头节点存在,确保wait status的值不为0,如果为0,则不需要通知后继节点。

4.恢复后继节点的线程

后继节点持有待unpark的线程,这里的后继节点也就是next节点。、

Case 1:如果头节点等待状态小于0,清除等待状态。如果后继节点(P1)的状态不是取消,就恢复后继节点的线程,这样后继节点就可以重新获取锁。

这里写图片描述

Case 2:如果后继节点为cacelled或者null,那就从尾节点向前遍历找到non-callcelled节点。

这里写图片描述

一旦恢复的线程获取到锁,该节点就变为新的头节点,旧的头节点就会被释放。如果获取同步状态失败,就会重新挂起。头节点的等待状态 的值会从0重置为SINGAL。

5.共享式释放锁

这和独占式释放非常相似,同时也要确保释放的连续。

6.共享式获取锁

共享式获取同步状态和独占式获取同步状态相似,同时通知等待队列中等待获取共享锁的线程。一旦锁状态释放,恢复后继节点,同时后继节点传播释放状态到后继节点的next节点。

7.取消

在获取锁的过程如果遇到运行时异常,就会取消上下文节点。如果一个节点被取消,必须确保该节点的后继节点可以指向一个有效的前驱节点,节点之间的关系可能需要重新调整。如果前驱节点已经是取消状态了,跳过这些取消状态的节点,到达一个正确的前驱节点(wait status <=0)。

如果要取消的节点正好是尾节点,想对来说比较简单,移除即可,该节点的前驱节点变为新的尾节点,新节点的‘next’指针指向null。
如果wait status <0,意味着后继节点需要接收通知信号,设置前驱节点的next指针指向一个后继节点。如果前驱节点正好是头节点,唤醒后继节点。
Case 1:要取消的节点正好是尾节点。

这里写图片描述

Case 2:要取消的节点的前驱节点是头节点。

这里写图片描述

8.总结:

AQS的设计是模板方法的一个典型应用,在不同的类中,acquire和release操作的名字和形式不同,例如Lock.lock,CountDownLatch.await,Semaphore.acquire和FutureTask.get,模板方法都是final类型,子类只需实现tryAcquire和tryRelease即可。


更多内容欢迎关注公众号“大象小蚁”
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值