AQS实现原理
AQS(AbstractQueuedSynchronizer)即 队列同步器,是⽤来构建锁或者其他同步组件的基础框架,它使⽤了⼀个int成员变量表⽰同步状态,通过内置的FIFO队列来完成资源获取线程的排队⼯作。
同步器的主要使⽤⽅式是继承。⼦类推荐被定义为⾃定义同步组件的静态内部类,同步器⾃⾝没有实现任何同步接⼝,它仅仅是定义了若⼲同步状态获取和释放的⽅法来供⾃定义同步组件使⽤,同步器既可以⽀持独占式地获取同步状态,也可以⽀持共享式地获取同步状态。
同步器的设计是基于模板⽅法模式的,也就是说,使⽤者需要继承同步器并重写指定的⽅法,随后将同步器组合在⾃定义同步组件的实现中,并调⽤同步器提供的模板⽅法,⽽这些模板⽅法将会调⽤使⽤者重写的⽅法。重写同步器指定的⽅法时,需要使⽤同步器提供的如下3个⽅法来访问或修改同步状态。
·getState():获取当前同步状态。
·tState(int newState):设置当前同步状态。
·compareAndSetState(int expect,int update):使⽤CAS设置当前状态,该⽅法能够保证状态设置的原⼦性。
同步器提供的模板⽅法基本上分为3类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况。⾃定义同步组件将使⽤同步器提供的模板⽅法来实现⾃⼰的同步语义。
下⾯看下 同步器可被重写的⽅法如下(如,ReentrantLock就重写了下⾯的某些⽅法)
同步器可重写的⽅法如图:payoff
下⾯我们就举个例⼦,看独占锁(独占锁就是在同⼀时刻只能有⼀个线程获取到锁,⽽其他获取锁的
线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁)是如何利⽤AQS,和重写他的⽅法来达到独占锁的效果的。
public class Mutex implements Lock {
// 静态内部类,⾃定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于占⽤状态
protected boolean isHeldExclusively() {
return getState() == 1;
hackett
}
// 当状态为0的时候获取锁
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
广州公园前
tExclusiveOwnerThread(Thread.currentThread());
haveanicedayreturn true;
}
return fal;
}
// 释放锁,将状态设置为0
protected boolean tryRelea(int releas) {
if (getState() == 0) throw new
IllegalMonitorStateException();
tExclusiveOwnerThread(null);
tState(0);
七年级上册英语单词表return true;
高考报考指南2013}
// 返回⼀个Condition,每个condition都包含了⼀个condition队列
Condition newCondition() { return new ConditionObject(); }
}
// 仅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
everyone是什么意思public void lock() { sync.acquire(1); }
public boolean tryLock() { Acquire(1); }
public void unlock() { lea(1); }
public Condition newCondition() { wCondition(); }
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 {
AcquireNanos(1, Nanos(timeout));
}
}
Mutex中定义了⼀个静态内部类,该内部类继承了同步器并实现了独占式获取和释放同步状态。在tryAcquire(int acquires)⽅法中,如果经过CAS设置成功(同步状态设置为1),则代表获取了同步状态,⽽在tryRelea(int releas)⽅法中只是将同步状态重置为0。⽤户使⽤Mutex时并不会直接和内
cec部同步器的实现打交道,⽽是调⽤Mutex提供的⽅法,在Mutex的实现中,以获
取锁的lock()⽅法为例,只需要在⽅法实现中调⽤同步器的模板⽅法acquire(int args)即可。
下⾯我们队队列同步器AQS的实现进⾏分析。主要包括:同步队列、独占式同步状态获取与释放、共享式同步状态获取与释放以及超时获取同步状态等同步器的核⼼数据结构与模板⽅法。
同步器依赖内部的同步队列(⼀个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为⼀个节点(Node)并将其加⼊同步队列,同时会阻塞当前线程,当同步状态释放时,会把⾸节点中的线程唤醒,使其再次尝试获取同步状态。节点(Node)⽤来保存获取同步状态失败的线程引⽤、等待状态以及前驱和后继节点,具体的定义和状态⼤家可以⾃⾏去看下源码这⾥略过。同步队列的基本结构如图所⽰:
同步器包含了两个节点类型的引⽤,⼀个指向头节点,⽽另⼀个指向尾节点。试想⼀下,当⼀个线程成功地获取了同步状态(或者锁),其他线程将⽆法获取到同步状态,转⽽被构造成为节点并加⼊到同步队列中,⽽这个加⼊队列的过程必须要保证线程安全,因此同步器提供了⼀个基于CAS的设置尾节点的⽅法:compareAndSetTail(Node expect,Nodeupdate),它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建⽴关联。
whiteflag同步队列遵循FIFO,⾸节点是获取同步状态成功的节点,⾸节点的线程在释放同步状态时,将会唤醒后继节点,⽽后继节点将会在获取同步状态成功时将⾃⼰设置为⾸节点。
下⾯分别看下独占式同步状态获取与释放和共享式同步状态获取与释放是如何实现的。
1)独占式同步状态获取与释放
通过调⽤同步器的acquire(int arg)⽅法可以获取同步状态,该⽅法对中断不敏感,也就是由于线程获取同步状态失败后进⼊同步队列中,后续对线程进⾏中断操作时,线程不会从同步队列中移出,该⽅法代码如下:
上述代码主要完成了同步状态获取、节点构造、加⼊同步队列以及在同步队列中⾃旋等待的相关⼯作,其主要逻辑是:⾸先调⽤⾃定义同步器实现的tryAcquire(int arg)⽅法,该⽅法保证线程安全的获取同步状态,如果同步状态获取失败,则构造同步节点并通过
addWaiter(Node node)⽅法将该节点加⼊到同步队列的尾部,最后调⽤acquireQueued(Node node,int arg)⽅法,使得该节点以“死循环”的⽅式获取同步状态。如果获取不到则阻塞节点中的线程,⽽被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。
我们看下同步器的addWaiter和enq的实现代码如下:
上述代码通过使⽤compareAndSetTail(Node expect,Node update)⽅法来确保节点能够被线程安全添加。
在enq(final Node node)⽅法中,同步器通过“死循环”来保证节点的正确添加,在“死循环”中只有通过CAS将节点设置成为尾节点之后,当前线程才能从该⽅法返回,否则,当前线程不断地尝试设置。
节点进⼊同步队列之后,就进⼊了⼀个⾃旋的过程(acquireQueued⽅法),每个节点(或者说每个线程)都在⾃省地观察,当条件满
⾜,获取到了同步状态,就可以从这个⾃旋过程中退出,否则依旧留在这个⾃旋过程中(并会阻塞节点的线程),如代码所⽰
在acquireQueued(final Node node,int arg)⽅法中,当前线程在“死循环”中尝试获取同步状态,⽽只有前驱节点是头节点才能够尝试获取同步状态。
独占式同步状态获取流程,也就是acquire(int arg)⽅法调⽤流程,如图所⽰
下⾯看下如何释放释放同步状态的:
time is money
该⽅法执⾏时,会唤醒头节点的后继节点线程,unparkSuccessor(Node node)⽅法使⽤LockSupport(这⾥不做介绍)来唤醒处于等待状态的线程。