AQS源码解析

AQS源码解析

AQS(AbstractQueuedSynchronizer)队列同步器是用来构建锁和其他同步组件的基础框架,基于cas无锁实现高效的同步机制,它底层维护了一个volatile整型变量和一个先进先出的线程等待队列。它提供独占和共享(多线程进入临界区)的两种资源共享方式,支持中断,超时和适应性自旋。

AQS实现原理

实现锁有Lock接口及其实现和AQS两大核心部分:

1.Lock接口定义了使用者和锁交互的细节,隐藏了实现细节

2.AQS则简化了锁的实现方式,封装屏蔽了同步状态管理,线程排队、等待唤醒这些锁独有的底层操作

AQS的设计基于模板方法,子类通过继承同步器并实现它的抽象方法来管理状态,对同步状态的更改通过getState(),setState(int new State),compareAndSetState(int expect, int update)来操作。

AQS可重写的方法在本类实现中都默认抛出异常,具体定义为:

 /**
* 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
  }

/**
* 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}

/**
* 共享式获取同步状态,返回大于等于0的值表示获取成功,否则获取失败
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}

/**
* 共享式释放同步状态
*/
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}

/**
* 判断当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程独占
*/ 
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}

AQS提供的模版方法基本分为三类:

1.独占式获取与释放同步状态

2.共享式获取与释放同步状态

3.查询同步队列中的等待线程情况

自定义同步组件将使用同步器提供的模板方法来实现自己的同步语义。 下面我们来看一个独占锁实例Mutex,Mutex实现了Lock接口,定义了面向用户交互的锁操作API,内部通过操作静态内部类Sync完成独占锁的获取和释放等操作,Sync屏蔽了锁的底层实现细节

 public class Mutex implements Lock {
private final Sync sync = new Sync();

/**
 * 静态内部类,自定义同步器
 */
private static class Sync extends AbstractQueuedSynchronizer {
    /**
     * 尝试cas添加独占锁,成功返回true,失败返回false
     */
    @Override
    protected boolean tryAcquire(int arg) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }


    /**
     * 尝试cas释放独占锁,成功返回true,失败返回false
     */
    @Override
    protected boolean tryRelease(int arg) {
        if (compareAndSetState(1, 0)) {
            setExclusiveOwnerThread(null);
            return true;
        } else {
            return false;
        }
    }

    /**
     * 是否处于占用状态
     */
    @Override
    protected boolean isHeldExclusively() {
        return super.getState() == 1;
    }

}
/**
 * 堵塞加锁
 */
@Override
public void lock() {
    sync.acquire(1);
}

/**
 * 非堵塞尝试加锁
 */
@Override
public boolean tryLock() {
    return sync.tryAcquire(1);
}

/**
 * 堵塞解锁
 */
@Override
public void unlock() {
    sync.release(1);
}

/**
 * 当前是否已加锁
 */
public boolean isLocked() {
    return sync.isHeldExclusively();
}

/**
 * 堵塞加锁,可以被中断
 */
@Override
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

/**
 * 在指定时间内堵塞加锁,超时退出,可被打断
 */
@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

/**
 * 当前不支持条件等待实现
 */
@Override
public Condition newCondition() {
    throw new UnsupportedOperationException();
}
      
 }

从上面看到,AQS封装了堵塞/超时加解锁、加入/唤醒线程队列等复杂底层实现,开发者利用AQS实现子类,只需关注核心加解锁的细节,大大降低了实现一个可靠自定义同步组件的门槛。

在JDK实现,常常通过实现子类Sync和常用锁组合在一起,常见的Sync类又分为公平实现和非公平实现两种:

1.对于公平锁,在线程尝试获取锁时,会先检查队列是否又前驱节点,有则入队列

2.对非公平锁,会直接尝试获取同步状态,非公平锁的效率比公平锁高,因为很大几率出现某个线程重复获取锁的情况,从而减少了线程上下文切换,但可能会造成某些线程饥饿。

对于加锁方式,往往也有两种模式,分别为独占模式和共享模式:

1.对于独占模式,AQS维护的volatile变量state在初始阶段往往为0,当有线程尝试加锁时,会通过cas的算法是state加1,并且对于同一线程,可通过不断累加来实现重入功能,通过减1实现解锁。在加锁的时候,如果state不为0或cas不成功,说明加锁失败,会进入等待队列

2.对于共享模式,任务可能会分为N个线程执行,state也响应初始化为n,每个线程执行完会使state减1,直到state为0,会unpack主调用线程,然后主调用线程会从await函数返回

AQS实现细节分析

AQS实现的核心细节包括:

1.同步队列实现

2.独占式同步状态获取与释放

3.共享式同步状态获取与释放

4.超时获取同步状态

同步队列实现

AQS内部依赖一个FIFO的双向队列来完成同步状态的管理,在当前线程获取同步状态失败时,同步器会将其加入同步队列,同时会堵塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

同步队列的每个节点以AbstractQueuedSynchronizer$Node类定义,内部保存获取同步状态失败的线程引用、等待状态以及前驱、后继节点、节点的属性类型和名称描述等。源码定义:

static final class Node {
// 模式,分为共享与独占
// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;        
// 结点状态
// CANCELLED,值为1,表示当前的线程由于等待超时或超时被取消
// SIGNAL,值为-1,后继节点的线程处于等待状态,当前节点的线程如果释放了同步状态或被取消,将会通知后续节点,是后续节点的线程得以运行
// CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中,当其他线程对Condition调用signal()方法后,该节点将会从等待队列中转移到同步队列,加入到对同步状态的获取中。
// PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
// INITIAL,值为0,初始状态。表示当前节点在sync队列中,等待着获取锁
static final int CANCELLED =  1;
static final int SIGNAL    = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;        

// 结点状态
volatile int waitStatus;        
// 前驱结点
volatile Node prev;    
// 后继结点
volatile Node next;        
// 结点所对应的线程
volatile Thread thread;        
// 下一个等待者
Node nextWaiter;

// 结点是否在共享模式下等待
final boolean isShared() {
    return nextWaiter == SHARED;
}

// 获取前驱结点,若前驱结点为空,抛出异常
final Node predecessor() throws NullPointerException {
    // 保存前驱结点
    Node p = prev; 
    if (p == null) // 前驱结点为空,抛出异常
        throw new NullPointerException();
    else // 前驱结点不为空,返回
        return p;
}

// 无参构造函数
Node() {    // Used to establish initial head or SHARED marker
}

// 构造函数
 Node(Thread thread, Node mode) {    // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}

// 构造函数
Node(Thread thread, int waitStatus) { // Used by Condition
    this.waitStatus = waitStatus;
    this.thread = thread;
}
}

AQS内部定义了3个核心成员变量:

 /**
* 同步队列头部
 */
private transient volatile Node head;

/**
* 同步队列尾部,没有成功获取同步状态的线程都会初始化成一个节点加入到队列的尾部
 */
private transient volatile Node tail;

/**
* 同步状态
*/
private volatile int state;

AQS通过head和tail维护了一个双向同步队列

private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

只有这一步操作成功后,才能真正开始建立原来的尾节点和新加入节点的关联关系,具体操作过程参考enq(final Node node):

// node是新加入的节点
private Node enq(final Node node) {
for (;;) {
    Node t = tail;
    if (t == null) { // Must initialize
        // 当tail=null时,说明队列中没有等待线程,将tail更新为head
        if (compareAndSetHead(new Node()))
            tail = head;
    } else {
        // 建立新节点前向关联关系
        node.prev = t;
        // 要添加原来尾节点和新节点的关联关系,即确定新节点为真正的尾节点前,需要先cas确保线程安全
        if (compareAndSetTail(t, node)) {
            // 添加原来尾节点和新节点的关联关系
            t.next = node;
            return t;
        }
    }
}
}

队列的头节点总是一个空节点,第二个节点开始才是有效节点

独占式同步状态获取与释放

在AQS,同步状态的获取可通过acquire(int arg)、acquireInterruptibly(int arg)、tryAcquireNanos(int arg, long nanosTimeout)、tryAcquire(int arg)这4个函数实现。 其中前3个是模版方法,内部都用到了tryAcquire(int arg)函数,tryAcquire在AQS中是个空实现,需要在子类中定义非堵塞同步状态尝试获取的逻辑,获取结果以布尔值返回。下面主要看看前3个函数的实现

acquire 堵塞获取独占式同步状态

acquire方法对中断不敏感,如果在线程进入同步队列后对其实施中断操作,线程不会从同步队列中退出,源码实现:

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       selfInterrupt();
}

1.tryAcquire(arg):先尝试非堵塞获取同步状态,如果获取成功函数直接调用结束。

2.addWaiter(Node.EXCLUSIVE):获取同步状态失败后,尝试将线程以独占模式添加到同步队列的尾部,同时返回当前线程节点。

3.acquireQueued(addWaiter(Node.EXCLUSIVE), arg):等价于:acquireQueued(new Node(Thread.currentThread(), Node.EXCLUSIVE), arg)。此函数完成的功能是同步队列中的结点不断尝试获取资源,如果获取失败则堵塞直到获取成功。最后返回是否线程被中断过,如果是,走第4步,否则退出函数调用

4.selfInterrupt():进入此分支说明当前线程获取同步状态被打断过,在这里重新进行线程中断。

addWaiter 将节点加入到同步队列尾部

addWaiter的方法逻辑是先初始化一个独占模式的节点,然后利用cas尝试快速插入到队列尾部,如果成功,说明没有并发竞争,结束调用。失败则进入enq逻辑。 源码如下:

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
    node.prev = pred;
    if (compareAndSetTail(pred, node)) {
        pred.next = node;
        return node;
    }
}
enq(node);
return node;
}

在enq中,以死循环(自旋锁)不断尝试CAS操作,直到插入成功。

 // node是新加入的节点
private Node enq(final Node node) {
for (;;) {
    Node t = tail;
    if (t == null) { // Must initialize
        // 当tail=null时,说明队列中没有等待线程,将tail更新为head
        if (compareAndSetHead(new Node()))
            tail = head;
    } else {
        // 建立新节点前向关联关系
        node.prev = t;
        // 要添加原来尾节点和新节点的关联关系,即确定新节点为真正的尾节点前,需要先cas确保线程安全
        if (compareAndSetTail(t, node)) {
            // 添加原来尾节点和新节点的关联关系
            t.next = node;
            return t;
        }
    }
}
}
acquireQueued堵塞尝试获取同步状态

当节点进入同步队列后,如果节点的前置节点为头节点,节点包含的线程就会尝试获取同步状态,获取失败后进入堵塞,直到已经拿到同步状态的线程释放同步状态,会唤醒这些堵塞的节点,再继续判断前置节点若为头节点,则继续尝试获取同步状态,以此不断循环,直到获取到同步状态。

acquire 堵塞获取独占式同步状态

acquire方法对中断不敏感,如果在线程进入同步队列后对其实施中断操作,线程不会从同步队列中退出,源码实现:

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       selfInterrupt();
}

1.tryAcquire(arg):先尝试非堵塞获取同步状态,如果获取成功函数直接调用结束。

2.addWaiter(Node.EXCLUSIVE):获取同步状态失败后,尝试将线程以独占模式添加到同步队列的尾部,同时返回当前线程节点。

3.acquireQueued(addWaiter(Node.EXCLUSIVE), arg):等价于:acquireQueued(new Node(Thread.currentThread(), Node.EXCLUSIVE), arg)。此函数完成的功能是同步队列中的结点不断尝试获取资源,如果获取失败则堵塞直到获取成功。最后返回是否线程被中断过,如果是,走第4步,否则退出函数调用

4.selfInterrupt():进入此分支说明当前线程获取同步状态被打断过,在这里重新进行线程中断。

addWaiter 将节点加入到同步队列尾部

addWaiter的方法逻辑是先初始化一个独占模式的节点,然后利用cas尝试快速插入到队列尾部,如果成功,说明没有并发竞争,结束调用。失败则进入enq逻辑。 源码如下:

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
    node.prev = pred;
    if (compareAndSetTail(pred, node)) {
        pred.next = node;
        return node;
    }
}
enq(node);
return node;
}

在enq中,以死循环(自旋锁)不断尝试CAS操作,直到插入成功。

// node是新加入的节点
private Node enq(final Node node) {
for (;;) {
    Node t = tail;
    if (t == null) { // Must initialize
        // 当tail=null时,说明队列中没有等待线程,将tail更新为head
        if (compareAndSetHead(new Node()))
            tail = head;
    } else {
        // 建立新节点前向关联关系
        node.prev = t;
        // 要添加原来尾节点和新节点的关联关系,即确定新节点为真正的尾节点前,需要先cas确保线程安全
        if (compareAndSetTail(t, node)) {
            // 添加原来尾节点和新节点的关联关系
            t.next = node;
            return t;
        }
    }
}
}
acquireQueued堵塞尝试获取同步状态

当节点进入同步队列后,如果节点的前置节点为头节点,节点包含的线程就会尝试获取同步状态,获取失败后进入堵塞,直到已经拿到同步状态的线程释放同步状态,会唤醒这些堵塞的节点,再继续判断前置节点若为头节点,则继续尝试获取同步状态,以此不断循环,直到获取到同步状态。

// sync队列中的结点在独占且忽略中断的模式下获取(资源)
final boolean acquireQueued(final Node node, int arg) {
// 标志
boolean failed = true;
try {
    // 中断标志
    boolean interrupted = false;
    for (;;) { // 无限循环
        // 获取node节点的前驱结点,简单获取prev,检查为空抛异常
        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);
}
}

final Node predecessor() throws NullPointerException {
Node p = prev;
// 如果前置节点为空,抛异常
if (p == null)
    throw new NullPointerException();
else
    return p;
}

头节点是一个空节点,因而setHead函数的实现如下:

private void setHead(Node node) {
// 复用当前节点对象
head = node;
// 头节点线程为空
node.thread = null;
// 头节点没有前置即诶但
node.prev = null;
}

在尝试获取同步状态失败后,会调用shouldParkAfterFailedAcquire和parkAndCheckInterrupt函数。先看shouldParkAfterFailedAcquire函数:

// 当获取(资源)失败后,检查并且更新结点状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱结点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1
    /*
     * 当前等待状态是可唤醒状态,因而可以安全地调用park堵塞,等待unpark唤醒
     */
    return true; 
if (ws > 0) { // 只有=1的情况,表示状态为CANCELLED
    /*
     * 表示前置节点已经被取消,更新当前节点的前置节点为前置的前置,直到当前节点的前置节点不是取消状态
     */
    do {
        node.prev = pred = pred.prev;
    } while (pred.waitStatus > 0);  
    // 找到pred结点前面最近的一个状态不为CANCELLED的结点,赋值pred结点的next域
    pred.next = node; 
} else { 
    // ws可能为PROPAGATE=-3 或无状态=0或CONDITION=-2
    //当为CONDITION状态,表示此节点在condition queue中。 
    // 比较并设置前驱结点的状态为SIGNAL,统一进入SIGNAL状态
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 
}
// 不能进行park操作
return false;
}

如果shouldParkAfterFailedAcquire返回true,说明前驱节点等待状态为SIGNAL,表示当前节点可以安全进行堵塞park操作。park操作在函数parkAndCheckInterrupt中实现:

private final boolean parkAndCheckInterrupt() {
// 堵塞等待
LockSupport.park(this);
// 返回当前线程是否被中断过,并会重置中断位
return Thread.interrupted();
}

最后,如果park过程线程被中断过,会设置interrupted=true,在acquireQueued结束调用,即节点被取消或获取到同步状态后返回

acquireInterruptibly 可被打断独占式获取同步状态

这里主要分析acquireInterruptibly和acquire实现不同的地方,先看源码:

public final void acquireInterruptibly(int arg)
    throws InterruptedException {
if (Thread.interrupted())
    throw new InterruptedException();
if (!tryAcquire(arg))
    doAcquireInterruptibly(arg);
}

在除此尝试获取资源失败后,进入doAcquireInterruptibly逻辑:

private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
    for (;;) {
        final Node p = node.predecessor();
        if (p == head && tryAcquire(arg)) {
            setHead(node);
            p.next = null; // help GC
            failed = false;
            return;
        }
        if (shouldParkAfterFailedAcquire(p, node) &&
            parkAndCheckInterrupt())
            // 主要区别在这里,当检测到线程被中断后,抛出中断异常。
            throw new InterruptedException();
    }
} finally {
    if (failed)
        cancelAcquire(node);
}
 }
tryAcquireNanos 可超时和被打断独占式获取同步状态

tryAcquireNanos的实现也非常简单:

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
    throws InterruptedException {
if (Thread.interrupted())
    throw new InterruptedException();
return tryAcquire(arg) ||
    doAcquireNanos(arg, nanosTimeout);
 }

核心在doAcquireNanos:

private boolean doAcquireNanos(int arg, long nanosTimeout)
    throws InterruptedException {
if (nanosTimeout <= 0L)
    return false;
// 先根据传入超时时长算出超时时间点
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
    for (;;) {
        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();
        // 小于0说明已经到达超时时间
        if (nanosTimeout <= 0L)
            // 返回失败,表示没有取到同步状态
            return false;
        // 满足park条件,且距离超时时长大于自旋超时时间
        if (shouldParkAfterFailedAcquire(p, node) &&
            nanosTimeout > spinForTimeoutThreshold)
            // 堵塞指定超时时间
            LockSupport.parkNanos(this, nanosTimeout);
        // 如果堵塞期间被打断过,则抛出中断异常。
        if (Thread.interrupted())
            throw new InterruptedException();
    }
} finally {
    if (failed)
        cancelAcquire(node);
}
}

在尝试堵塞指定超时时间前,会判断超时时长是否大于spinForTimeoutThreshold,如果小于spinForTimeoutThreshold,则不进入堵塞,而是不断自旋尝试获取,以此来提高同步性能。

cancelAcquire从队列中撤销获取同步状态

1.在acquireQueued、doAcquireInterruptibly、doAcquireNanos等函数的实现中,开始都会初始化一个失败标志failed=true,当函数调用成功获取到同步状态后,会将失败标志failed设为true,最后在函数退出前,如果failed=false,不是因为成功获取到同步状态而退出,就会执行cancelAcquire函数,具体做以下事情:

2.将当前节点Node从队列中移除
若Node存在有效前置节点,确保前置节点为SIGNAL状态,将前置节点的下一节点链接到有效的后置节点。
若Node不存在有效前置节点,说明需要唤醒后置节点尝试获取同步状态

不是因为成功获取到同步状态而退出,就会执行cancelAcquire函数,具体做以下事情:

1.将当前节点Node从队列中移除

2.若Node存在有效前置节点,确保前置节点为SIGNAL状态,将前置节点的下一节点链接到有效的后置节点。

3.若Node不存在有效前置节点,说明需要唤醒后置节点尝试获取同步状态

// 取消继续获取(资源)
private void cancelAcquire(Node node) {
// node为空,返回
if (node == null)
    return;
// 设置node结点的thread为空
node.thread = null;

// 保存node的前驱结点
Node pred = node.prev;
while (pred.waitStatus > 0) // 找到node前驱结点中第一个状态小于等于0的结点,即不为CANCELLED状态的结点
    node.prev = pred = pred.prev;

// 获取pred结点的下一个结点,用于处理当node是尾节点时,cas为null
Node predNext = pred.next;

// 设置node结点的状态为CANCELLED,方便在同步队列中被后置节点跳过
node.waitStatus = Node.CANCELLED;

// 如果node结点为尾结点,则设置尾结点为pred结点
if (node == tail && compareAndSetTail(node, pred)) {
    // 比较并设置pred结点的next节点为null
    compareAndSetNext(pred, predNext, null); 
} else { // node结点不为尾结点,或者比较设置不成功
    int ws;
    /** 
    * 下面一步的作用是,确保有效(非head)前置节点状态为SIGNAL时,且后置节点不是取消状态,更新前置节点的下一节点为后置节点
    * 如果prev无效或为取消状态(被并发修改),则尝试释放node的后继节点
    */
    // (pred结点不为头结点,并且pred结点的状态为SIGNAL)或者 pred结点状态小于等于0,并且比较并设置等待状态为SIGNAL成功,并且pred结点所封装的线程不为空
    if (pred != head &&
        ((ws = pred.waitStatus) == Node.SIGNAL ||
         (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
        pred.thread != null) { 
        // 待取消节点的后继
        Node next = node.next;
        // 后继不为空并且后继不为取消状态
        if (next != null && next.waitStatus <= 0) 
            // 比较并设置pred.next = next,即跳过node节点
            compareAndSetNext(pred, predNext, next); 
    } else {
        unparkSuccessor(node); // 释放node的后继结点
    }

    node.next = node; // help GC
}
}
release独占模式释放锁

release模版方法中调用了子类实现的tryRelease来释放同步状态,如果tryRelease返回false,表示释放失败,同步返回false,否则简单唤醒头节点的下一个节点

public final boolean release(int arg) {
// tryReease由子类实现,通过设置state值来达到同步的效果。
if (tryRelease(arg)) {
    Node h = head;
    // waitStatus为0说明是初始化的空队列
    if (h != null && h.waitStatus != 0)
        // 唤醒后续的结点
        unparkSuccessor(h);
    return true;
}
return false;
}

共享式同步状态获取与释放

共享同步状态获取

共享式或独占式获取的主要区别是同一时刻能否有多个线程同时获取到同步状态。应用场景如读写锁,可以用共享式同步实现读锁,用独占式同步实现写锁,读读不互斥,读写、写写互斥。

通过调用同步器的acquireShared(int arg)方法可以共享式地获取同步状态

public final void acquireShared(int arg) {
//如果没有许可了则入队等待
if (tryAcquireShared(arg) < 0)
    doAcquireShared(arg);
} 

private void doAcquireShared(int arg) {
// 添加队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
    boolean interrupted = false;
    // 等待前继释放并传递
    for (;;) {
        final Node p = node.predecessor();
        if (p == head) {
            int r = tryAcquireShared(arg);// 尝试获取
            if (r >= 0) {
                // 获取成功则前继出队,跟独占不同的是
                // 会往后面结点传播唤醒的操作,保证剩下等待的线程能够尽快 获取到剩下的许可。
                setHeadAndPropagate(node, r);
                p.next = null; // help GC
                if (interrupted)
                    selfInterrupt();
                failed = false;
                return;
            }
        }

        // p != head || r < 0
        if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
            interrupted = true;
    }
}
finally {
    if (failed)
        cancelAcquire(node);
}
}

大部分实现逻辑类似与独占获取同步状态,主要区别是在setHeadAndPropagate方法调用,在更新头节点更新头节点的同时,如果tryAcquireShared返回值>0(说明许可还有能够继续被线程acquire)且当前节点的下一个节点也是等待获取共享同步状态,则尝试释放下一个节点。先看setHeadAndPropagate实现:

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
 * 尝试唤醒后继的结点:
 * propagate > 0说明许可还有能够继续被线程acquire;
 * 或者新老head为空或非取消和初始化状态,则尝试唤醒后续节点
 * 
 */
if if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
    Node s = node.next;
    // 后继结是共享模式则尝试传播唤醒操作
    // 如果后继是独占模式,那么即使剩下的许可大于0也不会继续往后传递唤醒操作
    // 即使后面有结点是共享模式。
    if (s == null || s.isShared())
        // 唤醒后继结点
        doReleaseShared();
}
} 

再看doReleaseShared实现:

private void doReleaseShared() {
for (;;) {
    Node h = head;
    // 队列不为空且有后继结点
    if (h != null && h != tail) {
        int ws = h.waitStatus;
        // 不管是共享还是独占只有结点状态为SIGNAL才尝试唤醒后继结点
        if (ws == Node.SIGNAL) {
            // 将waitStatus设置为0
            if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                // cas失败,继续循环尝试
                continue; 
            // 唤醒头部后继结点
            unparkSuccessor(h);
            // 如果状态为0则更新状态为PROPAGATE,cas失败,继续循环尝试
        } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
            continue; 
    }
    // 没被修改过直接退出,如果过程中head被修改了则重试。
    if (h == head) 
        break;
}
}

观察doReleaseShared唤醒的是调用时head的下一个节点,注意在调用doReleaseShared前已经更新过head为当前获取到共享同步状态的节点了。

类似独占式获取同步状态,共享式获取同样支持被中断和超时退出,通过acquireSharedInterruptibly和tryAcquireSharedNanos完成,实现类似,这里不再赘述。

共享同步状态释放

共享同步状态释放在releaseShared中完成,类似的,在调用子类方法tryReleaseShared成功后,会调用doReleaseShared释放共享头节点,同时唤醒后续节点。

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
    doReleaseShared();
    return true;
}
return false;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值