该文章属于《Java并发编程》系列文章,如果想了解更多,请点击《Java并发编程之总目录》
前言
在上篇文章 《Java并发编程之锁机制之Lock接口》中,我们已经了解了,Java下整个Lock接口下实现的锁机制是通过AQS(这里我们将AbstractQueuedSynchronizer 或AbstractQueuedLongSynchronizer统称为AQS)
与Condition来实现的。那下面我们就来具体了解AQS的内部细节与实现原理。
PS:该篇文章会以
AbstractQueuedSynchronizer
来进行讲解,对AbstractQueuedLongSynchronizer有兴趣的小伙伴,可以自行查看相关资料。
AQS简介
抽象队列同步器AbstractQueuedSynchronizer (以下都简称AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量来表示同步状态,通过内置的FIFO(first-in-first-out)同步队列来控制获取共享资源的线程。
该类被设计为大多数同步组件的基类,这些同步组件都依赖于单个原子值(int)来控制同步状态,子类必须要定义获取获取同步与释放状态的方法,在AQS中提供了三种方法getState()
、setState(int newState)
及compareAndSetState(int expect, int update)
来进行操作。同时子类应该为自定义同步组件的静态内部类,AQS自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。
AQS类方法简介
AQS的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
修改同步状态方法
在子类实现自定义同步组件的时候,需要通过AQS提供的以下三个方法,来获取与释放同步状态。
- int getState() :获取当前同步状态
- void setState(int newState) :设置当前同步状态
- boolean compareAndSetState(int expect, int update) 使用CAS设置当前状态。
子类中可以重写的方法
- boolean isHeldExclusively():当前线程是否独占锁
- boolean tryAcquire(int arg):独占式尝试获取同步状态,通过CAS操作设置同步状态,如果成功返回true,反之返回false
- boolean tryRelease(int arg):独占式释放同步状态。
- int tryAcquireShared(int arg):共享式的获取同步状态,返回大于等于0的值,表示获取成功,反之失败。
- boolean tryReleaseShared(int arg):共享式释放同步状态。
获取同步状态与释放同步状态方法
当我们实现自定义同步组件时,将会调用AQS对外提供的方法同步状态与释放的方法,当然这些方法内部会调用其子类的模板方法。这里将对外提供的方法分为了两类,具体如下所示:
- 独占式获取与释放同步状态
- void acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则返回,否则进入同步队列等待,该方法会调用tryAcquire(int arg)方法。
- void acquireInterruptibly(int arg):与 void acquire(int arg)基本逻辑相同,但是该方法
响应中断
,如果当前没有获取到同步状态,那么就会进入等待队列,如果当前线程被中断(Thread().interrupt()
),那么该方法将会抛出InterruptedException。并返回 - boolean tryAcquireNanos(int arg, long nanosTimeout):
在acquireInterruptibly(int arg)的基础上
,增加了超时限制,如果当前线程没有获取到同步状态,那么将返回fase,反之返回true。 - boolean release(int arg) :独占式的释放同步状态
- 共享式获取与释放同步状态
- void acquireShared(int arg):共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,
与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态。
- void acquireSharedInterruptibly(int arg):
在acquireShared(int arg)的基本逻辑相同
,增加了响应中断。 - boolean tryAcquireSharedNanos(int arg, long nanosTimeout):
在acquireSharedInterruptibly的基础上
,增加了超时限制。 - boolean releaseShared(int arg) :共享式的释放同步状态
AQS具体实现及内部原理
在了解了AQS中的针对不同方式获取与释放同步状态(独占式与共享式
)与修改同步状态的方法后,现在我们来了解AQS中具体的实现及其内部原理。
AQS中FIFO队列
在上文中我们提到AQS中主要通过一个FIFO(first-in-first-out)来控制线程的同步。那么在实际程序中,AQS会将获取同步状态的线程构造成一个Node节点,并将该节点加入到队列中。如果该线程获取同步状态失败会阻塞该线程,当同步状态释放时,会把头节点中的线程唤醒,使其尝试获取同步状态。
Node节点结构
下面我们就通过实际代码来了解Node节点中存储的信息。Node节点具体实现如下:
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
Node节点是AQS中的静态内部类
,下面分别对其中的属性(注意其属性都用volatile 关键字进行修饰
)进行介绍。
- int waitStatus:等待状态主要包含以下状态
- SIGNAL = -1:当前节点的线程如果释放了或取消了同步状态,将会将当前节点的状态标志位SINGAL,用于通知当前节点的下一节点,准备获取同步状态。
- CANCELLED = 1:被中断或获取同步状态超时的线程将会被置为当前状态,且该状态下的线程不会再阻塞。
- CONDITION = -2:当前节点在Condition中的等待队列上,(关于Condition会在下篇文章进行介绍),其他线程调用了Condition的singal()方法后,该节点会从等待队列转移到AQS的同步队列中,等待获取同步锁。
- PROPAGATE = -3:与共享式获取同步状态有关,该状态标识的节点对应线程处于可运行的状态。
- 0:初始化状态。
- Node prev:当前节点在同步队列中的上一个节点。
- Node next:当前节点在同步队列中的下一个节点。
- Thread thread:当前转换为Node节点的线程。
- Node nextWaiter:当前节点在Condition中等待队列上的下一个节点,(关于Condition会在下篇文章进行介绍)。
AQS同步队列具体实现结构
通过上文的描述我们大概了解了Node节点中存储的数据与信息,现在我们来看看整个AQS下同步队列的结构。具体如下图所示:
在AQS中的同步队列中,分别有两个指针(你也可以叫做对象的引用),一个
head
指针指向队列中的头节点,一个
tail
指针指向队列中的尾节点。
AQS添加尾节点
当一个线程成功获取了同步状态(或者锁),其他线程无法获取到同步状态,这个时候会将该线程构造成Node节点,并加入到同步队列中,而这个加入