JavaAPI-AQS-同步器基础框架的原理

目标

基于源码,掌握AQS原理知识,每个主要方法的职责,如何实现一个同步器。

JDK版本

1.8

内容

简介

基于FIFO等待队列,提供了一个实现[阻塞锁和相关同步器]的基础框架

可作为多种同步器的基类,内部使用一个原子整数(state)表示状态(如锁是否被获取、锁重复获取次数等)。

子类必须实现可见性为protected的行为,以变更状态,也就是定义了何种状态表示锁被获取或被释放。其他方法负责排队和阻塞逻辑。

子类可以维护其他状态域,但是必须使用getState、setState、compareAndSetState这三个方法来操作原子整形,才可保证同步性。

说明

  • 子类应定义为non-public 内部类,为外部类提供同步性。
  • 子类可仅支持排他模式或共享模式,也可同时支持两种模式。排他模式下,若锁已被获取,则其他线程无法获取锁。对于共享模式,多个线程可同时获取锁。
  • 定义的ConditionObject类,可被支持排他模式的子类当做Condition的实现。
  • 提供 针对内部queue和condition 的观察方法
  • 仅对原子整形state进行序列化和反序列化

用法

子类必须实现如下方法,并在其中查询或操作state,其实就是定义如何获取 释放锁。

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

对实现的要求

  • 线程安全
  • 简短 且 不阻塞

Node

用来表示等待队列的节点。

node持有线程的控制信息,每个node持有单独一个线程。status域记录了线程是否阻塞(若前任是release的,则该node为signalled)。status域不负责控制线程是否获得lock。位于队列首位的线程将尝试获取锁,但是'首位'不保证获取成功,仅代表具有参与竞争的权利。所以当前released的线程可能需要再次等待。

请求入队,必须作为新tail且原子性操作tail;出队,必须原子性的操作head。

Node类结构

Node-域

静态域

表明node处于共享模式
static final Node SHARED = new Node();

表明node处于排他模式
static final Node EXCLUSIVE = null;

static final int CANCELLED =  1;
static final int SIGNAL    = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;

status域

volatile int waitStatus;

可用值

  • SIGNAL: 当前节点的后继 已经或即将 通过 park 成为阻塞的,所以当前node release或cancel时必须unpark 后继。
  • CANCELLED:当前node由于超时或中断而变为cancel。一旦成为cancel,将不会再改变,且不会再次阻塞。
  • CONDITION: 该node当前处于一个condition queue中。在转移到sync queue之前,不会将其视为同步队列node。
  • PROPAGATE: releaseShared 传播到其他node。在doReleaseShared中,仅head node可以设置为该状态,以确保传播继续。

初始化为0,作为condition node时设置为CONDITION;其他时候必须使用CAS改变数值。

prev域

volatile Node prev;

当前node依赖的前任node。进入队列时设置,离队时设置为null。当前任是cancel时,可以通过prev快速找到一个非cancel node,且一定存在,因为head node永远不会是cancel的,一定是node成功获取后才成为head。线程仅可以cancel其对应node,不可以cancel其他node 。

next域

volatile Node next;

当前node的后继node,当前node release后会unpark其后继。入队时不会设置,后继产生时才设置前任的next;处理cancelled前任过程中会改变next数据,出队时设置为null。为简化isOnSyncQueue,cancelled node的next设置为自身。

volatile Thread thread;
Node nextWaiter;

指向等待condition的下一个node,或表明shared模式。

Node-行为

  • 是否共享模式
final boolean isShared() {
      return nextWaiter == SHARED;
}
  • 获取前任node,前任为null则NPE
  • 构造函数
    • Node() 用于初始化head和SHARED标记
    • Node(Thread thread, Node mode) Used by addWaiter
    • Node(Thread thread, int waitStatus) Used by Condition

等待队列的head

private transient volatile Node head;

懒初始化,除了初始化外,只能通过setHead修改。若head存在,则它的waitStatus不可能是cancelled。

等待队列的tail

private transient volatile Node tail;

懒初始化,仅通过enq方法添加新的等待node。

同步状态

private volatile int state;

自旋或park的选择标准

static final long spinForTimeoutThreshold = 1000L;

纳秒数,timeout时间小于该值则自旋,否则park。

行为 

protected boolean tryAcquire(int arg)
尝试获取排它锁。用于实现Lock#tryLock()。
protected boolean tryRelease(int arg)
尝试修改state,以实现释放排它锁的目的。
protected int tryAcquireShared(int arg)
尝试获取共享锁。
protected boolean tryReleaseShared(int arg)
尝试修改state,以实现释放共享锁的目的。
protected boolean isHeldExclusively()
判断当前线程是否持有排它锁。该方法仅在ConditionObject中使用,所以在未使用condition的情况下,不需要再次定义该方式。

protected final int getState()
protected final void setState(int newState)

// 原子性改变同步state
protected final boolean compareAndSetState(int expect, int update)
// 入队,head初始化或插入队尾成为tail。自旋+cas
private Node enq(final Node node)

// 入队,先尝试查到队尾,失败则调用enq。
private Node addWaiter(Node mode)

// 唤醒后继。如果node.next的waitstatus>0,即cancelled,则从tail开始遍历前任,找到距离node最近的waitstatus<0的一个后继,然后unpark后继的线程。
private void unparkSuccessor(Node node)

// 共享模式,release锁。若head.waitstatus=signal,则status cas 为0,并唤醒后继;若status=0,则status cas为propagate;若上述操作完成后,发现head变了,则对新head进行如上操作。
private void doReleaseShared()

// node获取失败后,是否需要进行park。若pred的status是signal,则直接返回true;顺便做如下事情:若pred是canceled,则找到距离node最近的非cancelled的前任;否则,则尝试pred status cas为signal。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)

// 中断当前线程。
static void selfInterrupt()

// park当前线程,即当前线程不再接受线程调度。park结束后,检查当前线程是否中断。
private final boolean parkAndCheckInterrupt()

// 排他模式,已经入队的node进行获取锁。自旋(若node前任是head,尝试获取锁,成功则返回,否则park并检查是否由于线程中断导致park结束。)
final boolean acquireQueued(final Node node, int arg)

// 入队,自旋(若node前任是head,尝试获取锁,成功则返回,否则park,若park结束的原因是中断,则抛出线程中断异常)。
private void doAcquireInterruptibly(int arg)

// 在超时时间内尝试获取锁。入队,自旋(若node前任是head,尝试获取锁,成功则返回true;若剩余时间<0,则返回false;若需要park且剩余时间大于1000纳秒,则park(剩余时间);若线程中断,则抛出线程中断异常)。
private boolean doAcquireNanos(int arg, long nanosTimeout)

// 共享模式,获取锁。新建node并入队,自旋(若前任是head,则尝试获取共享锁,若成功,则设置head并是决定是否向后继传播共享。不是head,则检查是否需要park,若需要则进行park并检查是否由于线程中断导致park结束。)
private void doAcquireShared(int arg)

// 共享模式,获取锁,差异在于若由于线程中断导致park结束,则抛出线程中断异常。
private void doAcquireSharedInterruptibly(int arg)

// 与doAcquireSharedInterruptibly类似,差异在于每次获取锁失败后,需要检查剩余超时纳秒,决定返回、是否需要park或直接进行下一次循环。
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
// 获取排它锁,忽略线程中断。应与实现Lock#lock()
尝试一次获取排它锁tryAcquire,成功则返回;否则入队排他模式的新节点并自旋获取锁acquireQueued。
public final void acquire(int arg)

// 获取排它锁,若线程中断则抛出异常。用于实现Lock#lockInterruptibly
检查若线程中断,则抛出异常;否则尝试一次获取排它锁tryAcquire,成功则返回;否则入队排他模式的新节点并自旋获取锁doAcquireInterruptibly。
public final void acquireInterruptibly(int arg)

// 超时时间内获取排它锁,,若线程中断则抛出异常。用于实现Lock#tryLock(long, TimeUnit)。
检查若线程中断,则抛出异常;否则尝试一次获取排它锁tryAcquire,成功则返回;否则自旋获取锁doAcquireNanos。
public final boolean tryAcquireNanos(int arg, long nanosTimeout)

// 释放排它锁。用于实现Lock#unlock。
若tryRelease返回ture 且 head不为null 且 head.waitStatus!=0,则唤醒head的后继unparkSuccessor(head)
public final boolean release(int arg)

// 获取共享锁。
首先尝试获取共享锁tryAcquireShared,若失败则自旋获取doAcquireShared。
public final void acquireShared(int arg)

// 获取共享锁,若线程中断则抛出异常。
若线程中断则抛出异常,否则尝试获取共享锁tryAcquireShared,若失败则自旋获取doAcquireSharedInterruptibly。
public final void acquireSharedInterruptibly(int arg)

// 超时时间内获取共享锁,若线程中断则抛出异常。
若线程中断则抛出异常,否则尝试获取共享锁tryAcquireShared,若失败则自旋获取doAcquireSharedNanos。
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)


public final boolean releaseShared(int arg)

// 队列中是否有正在等待获取锁的线程
public final boolean hasQueuedThreads()

// 是否有线程已经获取到锁
public final boolean hasContended()

// 获取队列中第一个线程,即等待时间最长的线程。
public final Thread getFirstQueuedThread()

// 线程是否在队列中。
从tail开始,并通过前任完成队列遍历,判断线程是否在队列中。
public final boolean isQueued(Thread thread)

// 队列中第一个线程是否处于排他模式。可用于ReentrantReadWriteLock。
final boolean apparentlyFirstQueuedIsExclusive()

// 队列中是否存在比当前线程等待更长时间的其他线程。
    由于随时可能发生线程中断和超时而导致取消的情况,所以无法保证准确性。可能当前线程得到true,其他线程得到false。
    设计用于公平同步器。
    例如:公平、可重入、排他模式同步器,获取锁的过程
    protected boolean tryAcquire(int arg) {
       if (isHeldExclusively()) {
         // A reentrant acquire; increment hold count
         return true;
       } else if (hasQueuedPredecessors()) {
         return false;
       } else {
         // try to acquire normally
       }
     }}
public final boolean hasQueuedPredecessors()

// 队列中线程数量,不保证准确性。
public final int getQueueLength()

// 队列中线程集合,不保证准确性。
public final Collection<Thread> getQueuedThreads()
public final Collection<Thread> getExclusiveQueuedThreads()
public final Collection<Thread> getSharedQueuedThreads()

// node是否在同步队列syncQueue。若waitStatus为Condition或前任为null,则不再等待队列中。
final boolean isOnSyncQueue(Node node)

// node从condition队列移动待sync队列。
final boolean transferForSignal(Node node)

// 取消wait后,将node移动到sync队列
final boolean transferAfterCancelledWait(Node node)

// condition是否使用当前同步器作为锁。
public final boolean owns(ConditionObject condition)

// condition必须以当前同步器作为锁,是否有线程在等待这个condition
public final boolean hasWaiters(ConditionObject condition)

// 等待condition的线程数量
public final int getWaitQueueLength(ConditionObject condition)

// 等待condition的线程集合
public final Collection<Thread> getWaitingThreads(ConditionObject condition)

ConditionObject

AQS中的condition实现类。

// condition队列的第一个node 
private transient Node firstWaiter;

// condition队列的最后一个node
private transient Node lastWaiter;

行为

// 向condition队列尾部添加新的node
若last节点的waitStatus不是condition,即视为取消等待,对整个condition队列进行取消状态的清除处理;生成waitStatus为condition的新节点并放到condition队尾。
private Node addConditionWaiter()

// 从condition的队首开始,找到非取消的node,将其移动到sync队列。
private void doSignal(Node first)

// 从condition队首开始,将所有node移动到sync队列。
private void doSignalAll(Node first)


public final void signal()
public final void signalAll()
public final void awaitUninterruptibly()

condition wait,线程中断时不会抛出中断异常。

  • 添加新的condition node并入队condition队列;
  • sync队列从队首释放一次获取的锁;
  • 线程block,直到signal,即自旋(若不位于sync队列,则park);
  • 重新获取锁;
public final void await() throws InterruptedException

condition wait,线程中断时抛出中断异常。

  • 若线程中断,则抛出异常;
  • 添加新的condition node并入队condition队列;
  • sync队列从队首释放一次获取的锁;
  • 线程block,直到signal,即自旋(若不位于sync队列,则park);
  • 重新获取锁;

若上述block过程出现线程中断,则抛出异常。

public final long awaitNanos(long nanosTimeout)
public final boolean awaitUntil(Date deadline)
public final boolean await(long time, TimeUnit unit)

// 是否有线程在等待condition
protected final boolean hasWaiters()

// 等待condition的线程数量
protected final int getWaitQueueLength()

// 等待condition的线程集合
protected final Collection<Thread> getWaitingThreads()

Condition

 Condition对标Object monitor方法(如Object#wait Object#notify Object#notifyAll),与Lock一起使用,使得Lock对象可以具有多个等待队列。在使用Lock替代synchronized的地方,须使用condition替换Object monitor方法。

Condition提供如下含义:一个线程被挂起即等待,直到被其他线程唤醒。

Condition实例必须与具体Lock实例进行关联,为了保证这种关联,必须使用Lock#newCondition创建Condition,使用Condition时必须先获取锁

Condition可以提供不同于Object monitor的行为和语义。

await最好与循环一起使用。

结语

阅读完AQS源码,可以清晰了解获取锁的过程。自定义Lock时,仅需要实现几个方法即可,可以理解为“自定义什么锁”、“何种方式获取/释放锁”。例如Redis+AQS。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值