前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >多线程基础(十四):AbstractQueuedSynchronizer源码分析

多线程基础(十四):AbstractQueuedSynchronizer源码分析

作者头像
冬天里的懒猫
发布2020-11-12 11:04:05
5020
发布2020-11-12 11:04:05
举报
文章被收录于专栏:做不甩锅的后端

1.类结构及成员变量

1.1 类结构和注释

类AbstractQueuedSynchronizer是java并发包中的核心,是实现大部分并发工具类的底层工具类,现在对这个类的源码进行分析。

1.1.1 类结构

AbstractQueuedSynchronizer类的继承结构如下,其中AbstractOwnableSynchronizer是一个抽象类,其中只定义了部分需要实现的抽象方法。

其内部有两个核心的内部类,Node和ConditionObject。 需要注意的是,AbstractQueueSynhronizer类本身就是一个抽象类,其他的同步工具如果要使用AQS都需要先继承。

1.1.2 注释

在类前面的注释部分如下: 提供了一个用于实现依赖于FIFO等待队列的阻塞锁和相关的同步器(实现了信号灯、事件等)。此类旨在为大多数依赖单个原子的同步器提供基础操作。子类必须定义更改此类状态的受保护的方法,并定义该状态对于获取或者释放此对象而言意味着什么。鉴于这些原因,此类中的其他方法将执行所有的排队和阻塞机制,子类可以维护其他状态字段。但仅跟踪使用方法getState、setState、compareAndSetState进行原子更新的int的值的同步性。 子类应定义为用于实现其所在类的同步属性的非公共内部帮助器类,AbstractQueuedSynchronizer没有实现任何同步接口,相反,它定义了acquireInterruptible之类的方法,可以通过具体的锁和相关的同步器适当的调用这些方法来实现其公共方法。 此类支持默认的exclusive模式和shared模式。当以独立方式进行获取时,其他线程尝试进行获取不会成功。由多个线程获取的共享模式可能成功。此类并不理解这些机械上的区别,即当成功获取共享模式时,下一个等待线程如果存在,还必须确定它是否也可以获取。在不同模式下等待线程共享相同的FIFO队列。通常,实现子类仅支持这些模式之一,但如果可以在ReadWriteLock中发挥作用。仅支持互斥模式或仅共享模式的子类无需定义支持未使用的模式方法。 此类定义了一个内部类ConditionObject,用以支持独占模式的子类用作Condition实现。为isHeldExclusively方法报告是否针对当前线程专有的保留同步。使用当前getState值调用的方法,会完全释放此对象,并且给定已保存的状态值,acquire最终会将其恢复为先前的获取状态。否则,没有AbstractQueuedSynchronizer方法会创建这样的条件,因此,如果无法满足此约束,请不要使用它。ConditionObject的行为当然取决于其同步器实现的语义。 此类提供了内部队列的检查、检测和监视方法。可以根据需要使用AbstractQueuedSynchronizer将他们导出到类中以实现其同步机制。 此类的序列化仅存储基本的原子整数维护状态,因此,反序列化的对象的队列是空的,需要可序列化的典型子类将定义一个readObject方法,该方法在反序列化的时候将其恢复为已知的状态。 要将此类用作同步器的基础,请使用getState,或者setState和compareAndSetState。检查或修改同步状态,重新定义以下方法:

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively 默认情况下,这些方法中的每一个都会引发UnsupportedOperationException。这些方法必须在内部是线程安全的,并且通常应该简短而不阻塞。定义这些方法是only 支持使用此类的方法。所有其他方法都申明为final,因为它们不能独立变化。 你可能还会发现,从AbstractOwnableSynchronizer继承的方法对于跟踪拥有独占同步器的线程很有用。鼓励你使用它们,这将启用监视和诊断工具。以帮助用户确定哪些线程持有锁。 即使此类基于FIFO队列,它们也不会自动执行FIFO获取策略,独占同步的核心采用以下形式:
代码语言:javascript
复制
Acquire:
    while (!tryAcquire(arg)) {
       将线程入队
       阻塞当前线程
    }
 Release:
    if (tryRelease(arg))
        取消阻塞第一个排队的线程

共享模式与此类似,但可能涉及级联信号。

因为获取队列中的获取检查是在排队之前被调用的,所以新获取的线程可能在被阻塞和排队的其他线程之前插入。但是,如果需要,你可以定义tryAcquire或tryAcquireShared以通过内部调用的一种或者多种检查方法来禁用插入,从而提供一个fair FIFO获取顺序。特别是,如果hasQueuedPredecessors(一种专门为公平同步器设计的方法)返回true,则大多数公平器都可以定义tryAcquire返回false。其他变化是可能的。

对于默认插入,greedy、renouncement、convoy-avoidance策略,吞吐量的可伸缩性通常最高。尽管不能保证锁的公平性,也可以避免饥饿,但是允许在较早排队的线程之前对较早排队的线程进行重新竞争,并且每个重新争用都可以毫无偏向地成功抵御传入线程。而且,尽管获取不“旋转”在通常的意义上,它们可以在阻塞之前执行{@code tryAcquire}的多次调用并插入其他计算。如果仅短暂地保持排他同步,则这将带来旋转的大部分好处,而在没有同步时,则不会带来很多负担。如果需要的话,您可以通过在调用之前使用“快速路径”检查来获取方法来增强此功能,可能会预先检查hasContended和/或hasQueuedThreads以仅在同步器可能不这样做的情况下这样做争辩。 此类为同步提供了有效且可扩展的基础,部分原因是通过将其使用范围专门用于可以依靠int状态,获取和释放参数以及内部FIFO等待队列的同步器。如果这还不够,则可以使用java.util.concurrent.atomic类,您自己的自定义 java.util.Queue类和LockSupport较低级别构建同步器支持阻塞。

使用范例

这是一个不可重入的互斥锁定类,使用值0表示解锁状态,使用值1表示锁定状态。虽然不可重入锁并不严格要求记录当前所有者线程,但是无论如何,此类都这样做以使使用情况更易于监视。它还支持条件并公开一种检测方法:

代码语言:javascript
复制
class Mutex implements Lock, java.io.Serializable {
 
// Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
  // Reports whether in locked state
  protected boolean isHeldExclusively() {
    return getState() == 1;
  }

  // Acquires the lock if state is zero
  public boolean tryAcquire(int acquires) {
    assert acquires == 1; // Otherwise unused
    if (compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(Thread.currentThread());
      return true;
    }
    return false;
  }

  // Releases the lock by setting state to zero
  protected boolean tryRelease(int releases) {
    assert releases == 1; // Otherwise unused
    if (getState() == 0) throw new llegalMonitorStateException();
   setExclusiveOwnerThread(null);
    setState(0);
    return true;
  }

  // Provides a Condition
  Condition newCondition() { return new ConditionObject(); }

  // Deserializes properly
  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    setState(0); // reset to unlocked state
  }
} 
// The sync object does all the hard work. We just forward to it.
   private final Sync sync = new Sync();

   public void lock()                { sync.acquire(1); }
   public boolean tryLock()          { return sync.tryAcquire(1); }
   public void unlock()              { sync.release(1); }
   public Condition newCondition()   { return sync.newCondition(); }
   public boolean isLocked()         { return sync.isHeldExclusively(); }
   public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
   public void lockInterruptibly() throws InterruptedException {
     sync.acquireInterruptibly(1);
   }
   public boolean tryLock(long timeout, TimeUnit unit)
       throws InterruptedException {
     return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   }
}}

这是一个类似于CountDownLatch的类,只不过,它需要触发一个signal。由于这个锁是非排他性,因此使用shared获取和释放方法。

代码语言:javascript
复制
 class BooleanLatch {

   private static class Sync extends AbstractQueuedSynchronizer {
     boolean isSignalled() { return getState() != 0; }
     protected int tryAcquireShared(int ignore) {
       return isSignalled() ? 1 : -1;
     }

     protected boolean tryReleaseShared(int ignore) {
       setState(1);
       return true;
     }
   }

   private final Sync sync = new Sync();
   public boolean isSignalled() { return sync.isSignalled(); }
   public void signal()         { sync.releaseShared(1); }
   public void await() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
   }
 }}

1.2 成员变量及常量

与大多数数据结构复杂的集合类不同的是,AbstractQueuedSynchronizer类并没有用特别复杂的数据结构和二进制的控制字段。其主要的数据结构是一个链表。变量及常量如下:

代码语言:javascript
复制
/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 */
private transient volatile Node tail;

/**
 * The synchronization state.
 */
private volatile int state;

/**
 * Returns the current value of synchronization state.
 * This operation has memory semantics of a {@code volatile} read.
 * @return current state value
 */
 
 /**
 * The number of nanoseconds for which it is faster to spin
 * rather than to use timed park. A rough estimate suffices
 * to improve responsiveness with very short timeouts.
 */
static final long spinForTimeoutThreshold = 1000L;

上述内容见下表所示:

变量名

类型

说明

head

private transient volatile Node

表示等待队列的头部,除初始化方法之外,只能通过setHead方法进行修改,需要注意的是,如果head存在,则保证waitStatus不会变成CANCELLED状态。

tail

private transient volatile Node

表示等待队列的尾部,只能通过enq方法进行修改以添加新的等待节点

state

private volatile int

表示同步的状态

spinForTimeoutThreshold

static final long

采用纳秒数作为park的等待时间,这样可以大幅提高响应能力,这个默认值为1000L,单位为纳秒,这也是AQS类中的唯一一个常量。

2.构造方法

代码语言:javascript
复制
protected AbstractQueuedSynchronizer() { }

默认是一个空的构造方法,此使的state状态为0。 我们可以参考ThreadPoolExecutor类中的内部类Worker,其在继承AbstractQueuedSynchronizer之后,在其构造方法中通过 setState(-1)来设置AQS state的状态。

3.关键的内部类

3.1 Node

这是标识等待队列的节点类。

3.1.1 注释

这个类是CLH(Craig, Landin, and Hagersten)锁定队列的变体,CLH的锁通常用于自旋,相反,我们在AQS中则用于实现阻塞同步器,即使相同的策略,即将有关线程的某些控制信息保存在其节点中。每个节点中的status将跟踪线程是否应该阻塞,节点的前节点释放时会法出信号,否则,队列中的每个节点都充当一个特定通知样式的监视器,其中包含一个等待线程,虽然状态字段不控制是否授予线程锁定,线程可能会尝试获取它是否在队列中的第一位,但是并不能保证成功,它只是赋予了竞争权,因此,当前release的最新的等待线程可能需要重新等待。 如果需要加入clh锁,你可以自动将其作为新的尾部入队,如果需要出队,只需要对head指针进行设置。

代码语言:javascript
复制
      +------+  prev +-----+       +-----+
 head |      | <---- |     | <---- |     |  tail
      +------+       +-----+       +-----+

插入到CLH队列中只需要对tail指针进行一次原子操作,因此,从未排队到排队都有一个简单的原子型的分界点,同样,出队仅涉及更新头,但是,节点需要花费更多的精力来确定其后继者是谁,部分原因是要处理由于超时和中断而可能导致的取消操作。 prev链接在原始CLH锁中未使用,主要用于处理取消,如果取消某个节点,其后继节点通常会重新链接到未取消的前任节点,有关自旋锁情况下类似机制的说明,请参见Scott和Scherer的论文High-Performance Synchronization for Shared-Memory Parallel Programs 我们还使用next指针来实现阻塞机制,每个节点的线程ID都保留在其自身的节点中,因此前一个节点通过遍历下一个链接以确定它是哪个线程,从而通知和唤醒这个线程,确定后继节点必须避免与新排队的节点竞争来设置其前任节点的next字段,如果需要,可以通过在节点的后继者为空时,从原子更新的tail向后检查来解决此问题。(换句话说,下一个链接是一种优化,因此我们通常不需要向后扫描) 取消操作将一些保守性质引入了基本算法,由于我们必须对其他节点是否取消进行轮询,因此我们可能会遗漏没有注意到已取消的节点在我们前面或者后面,要解决这个问题,必须始终在取消时对后继者也取消,使得他们能够稳定在新的前任者之上,除非我们能够确定谁将担负这一责任的前任节点。 CLH队列需要一个虚拟标头节点才能开始。但是我们不会在构建过程中创建它们,因为如果没有争用,这将是浪费时间。而是构造节点,并在第一次争用时设置头和尾指针。 等待条件变量的线程使用相同的节点,但使用附加链接。条件只需要在简单(非并行)链接队列中链接节点,因为仅当它们专用时才可以访问它们。等待时,将节点插入条件队列。收到信号后,该节点将转移到主队列。状态字段的特殊值用于标记节点所在的队列。 感谢Dave Dice,Mark Moir,Victor Luchangco,Bill Scherer和Michael Scott以及JSR-166专家组的成员,对本课程的设计提出了有益的想法,讨论和批评。

3.1.2 常量
代码语言:javascript
复制
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
 * waitStatus value to indicate the next acquireShared should
 * unconditionally propagate
 */
static final int PROPAGATE = -3;

上述常量整理为下表:

名称

类型

说明

SHARED

static final Node

示节点在共享模式下等待的标记

EXCLUSIVE

static final Node

指示节点以排他模式等待的标记

CANCELLED

static final int

waitStatus值表示线程已被取消,int值为1

SIGNAL

static final int SIGNAL

表示后续线程需要执行unpark,值为-1

CONDITION

static final int

表示线程正在等待状态,值为-2

PROPAGATE

static final int

表示下一个节点获得共享状态应该无条件的被传播

3.1.3 变量
3.1.3.1 waitStatus

有关status状态字段的取值说明:

状态

说明

SIGNAL

表示该节点的后续节点将被阻塞(或者很快将要,通过park方法),因此当前节点释放或者取消的时候,必须对其后续节点unpark,为了避免冲突,acquire方法必须首先指示他们需要的信号,然后重新进行原子型的获取,然后在失败的时候阻塞

CANCELLED

由于超时或者中断导致该节点被取消,节点永远不会离开这个状态,具有取消的节点,永远不会在此被阻塞

CONDITION

该节点当前在条件队列中,在传输之前,它不会用作同步队列节点,此状态将设置为0,此值的使用与该字段的其他状态无关,对该机制进行了简化

PROPAGATE

releaseShared应该传播到其他节点,在doReleaseShared对此进行了设置,仅适用于头节点,以确保传播继续进行,即使此后进行了其他操作也是如此。

0

不是以上任何一种情况

这些值以数字的方式排列以简化使用,非负值表示节点不需要发信号,因此,大多数代码不需要检查特定值,仅需检查符号即可。对于常规的同步节点,该字段初始化为0,对于条件节点,该字段初始化为CONDITION,使用CAS(或者在可能的情况下进行无条件的volatile写操作)进行修改。

3.1.3.2 其他变量

其他变量见下表:

变量

类型

说明

prev

volatile Node

链接到当前节点/线程用来检查waitStatus的先前节点。在入队期间分配,并且仅在出队的时候将其清空(出于GC的考虑),同样,在取消前任后,我们会短路,同时找到一个未取消的前任,这将一直存在,因为跟节点永远不会被取消,只有成功获取之后,节点才变成根。被取消的线程永远不会成功获取,并且一个线程只会取消自身,而不会取消任何其他节点。

next

volatile Node

链接到后继节点,当前节点/线程在释放时将其解散,在排队过程中分配,在绕过取消的前任对象时进行调整,在出队时无效(出于对GC的考虑)。enq操作,直到附加后才分配前任节点的下一个字段,因此看到空的下一个字段不一定表示节点在队列的末尾。但是,如果下一个字段可能为空,则我们可以从尾部扫描上一个以进行再次检查,被取消节点的下一个字段设置指向节点本身而不是null,以使得isOnSyncQueue的工作更轻松

thread

volatile Thread

使该节点排队的线程。在构造上初始化,使用后消失。

nextWaiter

Node

链接到等待条件的下一个节点,或者链接到特殊值SHARED。由于条件队列仅在以独占模式保存时才被访问,因此我们只需要一个简单的链表队列即可以在节点等待条件时保存节点,然后他们在转移到队列的过程中以重新获取。由于条件只能是互斥的,因此我们使用特殊值来表示共享模式来保存字段。

3.1.4 构造函数

Node是AQS的基本单元,其构成了AQS的等待队列和Condition的条件队列。结构如下:

Node提供的构造函数一共有3种,分别是用于初始化和添加waiter,以及通过Condation。

3.1.4.1 Node()
代码语言:javascript
复制
Node() {    // Used to establish initial head or SHARED marker
}
3.1.4.2 Node(Thread thread, Node mode)

此方法用于添加waiter,只需要初始化thread,node。

代码语言:javascript
复制
Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}
3.1.4.3 Node(Thread thread, int waitStatus)

此方法用于Condation,初始化thread、waitStatus。

代码语言:javascript
复制
Node(Thread thread, int waitStatus) { // Used by Condition
    this.waitStatus = waitStatus;
    this.thread = thread;
}

3.2 ConditionObject

ConditionObject的实现也是一个队列,实际上其内部是由Node组成的链表。在这个类种定义了首尾两个指针。|

代码语言:javascript
复制
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;

变量名称

类型

说明

firstWaiter

private transient Node

条件队列的第一个节点。

lastWaiter

private transient Node

条件队列的最后一个节点。

其结构如下:

ConditionObject主要是用于ReentrantLock等锁对象的时候,作为派生的条件变量Condition。一个Lock可以通过newCondition方法,派生出多个Condition对象。而一个Condition对象就是一个队列,此使复用了AQS类种的Node节点,这个Condition实际上只是用到了nextWaiter指针,是一个单向链表结构。 这样构成的Condition队列如下:

由于ConditionObject的方法较为复杂,不在本文中详细描述,后面单独来讲。

4.基本原理

AbstractQueueSynchronizer是java并发包中的核心部分,大多数同步工具,如ReentrantLock、CountDownLantch等都是构建在AbustactQueueSynchronizer之上的应用对象。因此,理解好AbstaractQueueSynchronizer之后,有利于理解并发包中的其他应用类。 经过前面的描述,我们可以直到AQS的基本构成:

AQS实际上是一个双向链表组成的队列,Node是AQS的基本构成节点,其内部指针通过prev和next来双向描述链表,而nextWaiter指针则专门用于Condition。每个Condition会有一个单独的队列。 在传统的理解上,我们总将synhronized与ReentrantLock进行等价,认为ReentrantLock就是与synchronized类似的锁,实际上,ReentrantLock依赖于AQS来实现,而AQS本身并不是什么锁。AQS采用标记状态+队列来实现,记录获取锁、竞争锁、释放锁的一系列操作,其并不关心什么是锁,而是采用了一些列判断资源是否可以访问的API,并且对访问资源受限的时候,对请求线程的操作进行封装,如加入队列、挂起、唤醒等操作。对于线程的操作将采用LockSupport的park和unpark方法。在前面学习LockSupport的时候学过,LockSupport底层是使用的UnSafe类提供的方法。而AQS本身也大量采用了UnSafe提供的底层API实现,这体现在CAS操作之上。 对于AQS,我们需要关心三类问题。

  • 资源的访问方式,是同时支持多个线程访问,还是只能允许一个线程访问?
  • 资源如果访问的时候无法获得,将如何处理?
  • 如果有线程等待的时间过长,不想继续等待,又将如何处理?

这些问题将是我们学习AQS的一些基本思路。对此,关于支持多线程还是单线程访问的问题,这样就有了独占和共享两种模式,AQS分别对于独占和共享提供了相关的API方法,而其子类,要么实现了独占,如ReentrantLock,要么实现了共享如ReentrantReadWriteLock。任何一个子类都不会同时实现两套API。 因此,对于ReentrantLock,资源是否可以访问,则可定义为,只要AQS的state状态不为0,并且持有线程不为当前线程,则代表资源不可访问。 而对于CountDownLatch等,资源是否可以访问,则定义为,只要AQS的状态不为0,则代表资源不可访问。

上文中的关于资源如果无法获得,将如何处理,这个答案很显然,排队,再队列中等待。如果不想等了,想取消,那么AQS也定义了很多关于取消的API。这将再后续方法中描述。

5.AQS方法API

AQS其目的是为了实现一个Lock,那么与要实现的Lock对应的话,需要实现Lock接口。然后再实现一个所谓的锁。这可以参考前面的注释代码。ReentrentLock也同理。AQS还分为共享和独占两种实现,那么与Lock对照如下:

实际上这个实现关心也可以用如下表来说明:

锁方法

AQS实现

说明

lock()

acquire(1)/acquireShared(1)

获得锁,通过独占或者共享方法都能实现,传入的参数是1,这个锁不允许中断,如果调用中断方法将会无响应。

lockInterruptibly()

accquireInterruptibly(1)/acquireSharedInterruptibly(1)

获得可以中断的锁,支持独占和共享两种方式。

tryLock()

tryAcquire(1)/tryAcquireShared(1)

尝试获得锁,独占和共享都可以实现,但是不支持超时,会无限等待。

tryLock(timeout)

tryAcquireNanos(1,nanos)/tryAcquireSharedNanos(1,nanos)

支持超时时间的tryLock方法,当超时时间达到之后,不再等待。

unlock()

release(1)/releaseShared(1)

释放锁,可以通过共享或者独占的方式调用

unlock()

tryRelease(1)/tryReleaseShared(1)

unlock的时候,需要调用tryRelease尝试释放锁。

newCondition()

newCondition()

这个方法将new一个条件变量ConditionObject,之后通过Condition产生的等待线程都将进入这个等待队列

hasQueuedThreads()

hasQueuedThreads()

判断该队列中是否存在等待的线程,通常用head和tail对比是否相等来返回true和false。

下面再看看这些常用方法是如何实现的。

5.1 acquire

acquire采用独占模式来实现,不可中断,然后通过至少调用一次tryAcquire来实现。如果一次不能成功,线程将排队,可能反复被阻塞与取消阻塞,循环调用tryAcquire直到成功。这个方法可以用于被子类实现。

代码语言:javascript
复制
public final void acquire(int arg) {
    //如果调用tryAcquire方法不成功且acquireQueued入队不成功,则调用自我中断。
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

这个方法的逻辑很明显,调用tryAcquire,如果成功则获得锁,如果成功,则调用acquireQueued入队,再不成功则自我中断。 tryAcquire 这个方法需要子类单独实现。

5.2 acquireQueued

此方法,主要是通过循环,多次尝试获得锁。调用tryAcquire方法。反之则调用park方法进行等待。如果这个过长产生异常,则调用cancelAcquire方法进行取消。

代码语言:javascript
复制
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

5.3 acquireShared

如果采用共享式的获取锁的方法: 则将会调用tryAcquireShared方法,如果不成功则调用doAcquireShared。

代码语言:javascript
复制
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

5.4 doAcquireShared

代码如下:

代码语言:javascript
复制
private void doAcquireShared(int arg) {
    //添加一个共享节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //拿到p的前一个节点
            final Node p = node.predecessor();
            //如果p不为head
            if (p == head) {
                //则说明p没有获得锁,则p继续调用try方法
                int r = tryAcquireShared(arg);
                //如果try方法返回结果大于0,则说明互获得锁 
                if (r >= 0) {
                    //将当前节点移除
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    //如果出现异常,则自我中断
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //如果p为head节点,则调用shouldParkAfterFailedAcquire对节点状态和指针进行check。如果通过,则将线程park,之后等待被唤醒,被唤醒之后调用中断方法。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //如果失败,则调用取消入队方法
        if (failed)
            cancelAcquire(node);
    }
}

可以看出共享和独占模式首先Node的状态不同。

5.5 acquireInterruptibly

实现一个可中断的lock方法。 代码如下:

代码语言:javascript
复制
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
        //判断中断状态
    if (Thread.interrupted())
        throw new InterruptedException();
        //调用tryAcquire方法
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

实际上可中断状态的lock方法,只是在一开始就判断了Thread的interrupt状态。之后如果tryAcquire不成功的话,调用doAcquireInterruptibly。这个方法又是一个循环调用tryAcquire的方法。

5.6 doAcquireInterruptibly

调用doAcquireInterruptibly方法。

代码语言:javascript
复制
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    //创建一个独占类型的节点。
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        //死循环
        for (;;) {
            //拿到节点的上一个节点
            final Node p = node.predecessor();
            //如果为head且调用tryAcquire方法成功,则将当前节点设置为head,将前一个节点移除。
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            //执行shouldParkAfterFailedAcquire检查。
            if (shouldParkAfterFailedAcquire(p, node) &&
               //执行park并检查中断
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
       //如果状态为false,则取消
        if (failed)
            cancelAcquire(node);
    }
}

5.7 acquireSharedInterruptibly

此方法与acquireInterruptibly类似

代码语言:javascript
复制
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

首先判断中断状态,之后再调用doAcquireSharedInterruptibly。

5.8 doAcquireSharedInterruptibly

这是共享模式获取锁的主要执行方法,与独占模式类似。

代码语言:javascript
复制
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //定义一个共享模式的节点。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
       //死循环
        for (;;) {
            //获取前节点
            final Node p = node.predecessor();
            //如果前节点为head,则调用tryAcquireShared
            if (p == head) {
                int r = tryAcquireShared(arg);
                //如果tryAcquireShared结果大于0则说明获取锁成功,将前节点清除
                if (r >= 0) {
                    //设置当前节点为head,且调用release方法
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //调用检查方法
            if (shouldParkAfterFailedAcquire(p, node) &&
                //执行park和中断 
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
       //如果失败则取消入队
        if (failed)
            cancelAcquire(node);
    }
}

5.9 doAcquireNanos

这个方法是实现tryAcquireNanos的关键方法。

代码语言:javascript
复制
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //判断超时时间的有效性
    if (nanosTimeout <= 0L)
        return false;
    //根据超时时间计算deadline
    final long deadline = System.nanoTime() + nanosTimeout;
    //添加Node
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
       //死循环
        for (;;) {
            //判断上一个节点是否为head,且是否获得锁
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            //判断是否已经超时
            nanosTimeout = deadline - System.nanoTime();
            //如果超时则返回false 退出
            if (nanosTimeout <= 0L)
                return false;
            //反之则调用参数检查
            if (shouldParkAfterFailedAcquire(p, node) &&
                //如果此使超时时间大于1000纳秒
                nanosTimeout > spinForTimeoutThreshold)
                //调用带超时参数的park方法
                LockSupport.parkNanos(this, nanosTimeout);
            //如果中断则抛出中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
       //如果失败则取消入队
        if (failed)
            cancelAcquire(node);
    }
}

5.10 doAcquireSharedNanos

对u有doAcquireSharedNanos方法,实际上与doAcquireNanos类似。

代码语言:javascript
复制
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //判断超时时间
    if (nanosTimeout <= 0L)
        return false;
    //计算deadline
    final long deadline = System.nanoTime() + nanosTimeout;
    //此处添加共享模式的节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
       //死循环
        for (;;) {
            //获得前节点
            final Node p = node.predecessor();
            //判断前节点是否为head
            if (p == head) {
                //用共享模式再次获取锁
                int r = tryAcquireShared(arg);
                //如果成功
                if (r >= 0) {
                   //设置head状态并唤醒
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                //执行带超时的park方法
                LockSupport.parkNanos(this, nanosTimeout);
            //线程中断
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        //如果失败则取消
        if (failed)
            cancelAcquire(node);
    }
}

5.11 release

代码语言:javascript
复制
public final boolean release(int arg) {
    //调用tryRelease方法
    if (tryRelease(arg)) {
        //定义head
        Node h = head;
        //如果head不为空,且等待状态不为0
        if (h != null && h.waitStatus != 0)
            //将后续的节点都执行unpark
            unparkSuccessor(h);
        //之后返回true
        return true;
    }
    //如果释放不成功则返回false
    return false;
}

与acquire方法类似,需要子类实现tryRelease方法。

5.12 releaseShared

代码语言:javascript
复制
public final boolean releaseShared(int arg) {
   //调用try方法
    if (tryReleaseShared(arg)) {
       //调用do方法
        doReleaseShared();
        return true;
    }
    return false;
}

5.13 unparkSuccessor

这个方法将独占的后续Node全部唤醒。

代码语言:javascript
复制
private void unparkSuccessor(Node node) {
   //判断状态如果为负数,则清理,将其改为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //s为后续节点
    Node s = node.next;
    //如果s不为空,且s状态大于0
    if (s == null || s.waitStatus > 0) {
        s = null;
        //循环遍历
        for (Node t = tail; t != null && t != node; t = t.prev)
            //如果t的状态小于0 将s赋值为t 
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //将s对应的线程唤醒
        LockSupport.unpark(s.thread);
}

5.14 doReleaseShared

共享方法的释放锁过程,与独占模式不同的是,独占状态,只需要判断当前节点是否为空,之后修改当前节点的状态,并执行unpark。而共享模式,则除了判断是否为空之外,还需要判断状态的具体情况。

代码语言:javascript
复制
 private void doReleaseShared() {
//死循环
for (;;) {
    Node h = head;
    //如果h不为空
    if (h != null && h != tail) {
        int ws = h.waitStatus;
        //获得h的状态为ws,如果为SIGNAL
        if (ws == Node.SIGNAL) {
            //将其改为0 如果修改失败则ccontinue 下次循环继续修改
            if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                continue;            // loop to recheck cases
            //将后续的node调用unpark
            unparkSuccessor(h);
        }
        //反之 如果ws状态不为SIGNAL 则 将其从0改为PROPAGATE状态 如果失败则continue
        else if (ws == 0 &&
                 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
            continue;                // loop on failed CAS
    }
    //如果h为head 则直接break结束循环
    if (h == head)                   // loop if head changed
        break;
}
}

6.总结

AQS是一个模板方法类,用以实现ReentrantLock和CountDownLatch等并发工具。需要注意的是,state是标识资源被占用的状态,如果为0,则说明没有锁定。之后如果获得锁,则state加1,如果释放锁,则state减1,但是这个过程不是在AQS中实现的,tryAcquire,以及tryAcquireShared等方法,这需要具体的实现类来实现。 AQS本身的数据结构是一个以Node组成的链表,ConditionObject是AQS用以支持条件变量的实现,其本身也是一个单向链表组成的队列。 后面将在ReentrantLock等并发工具类中具体来说明AQS的实现细节。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/11/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.类结构及成员变量
  • 1.1 类结构和注释
    • 1.1.1 类结构
      • 1.1.2 注释
      • 1.2 成员变量及常量
      • 2.构造方法
      • 3.关键的内部类
        • 3.1 Node
          • 3.1.1 注释
          • 3.1.2 常量
          • 3.1.3 变量
          • 3.1.4 构造函数
        • 3.2 ConditionObject
        • 4.基本原理
        • 5.AQS方法API
          • 5.1 acquire
            • 5.2 acquireQueued
              • 5.3 acquireShared
                • 5.4 doAcquireShared
                  • 5.5 acquireInterruptibly
                    • 5.6 doAcquireInterruptibly
                      • 5.7 acquireSharedInterruptibly
                        • 5.8 doAcquireSharedInterruptibly
                          • 5.9 doAcquireNanos
                            • 5.10 doAcquireSharedNanos
                              • 5.11 release
                                • 5.12 releaseShared
                                  • 5.13 unparkSuccessor
                                    • 5.14 doReleaseShared
                                      • 6.总结
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档