深入AbstractQueuedSynchronizer源码及锁特性

640 阅读4分钟

背景介绍

我们知道,基于AbstractQueuedSynchronizer线程同步器可以很方便的实现锁的一些高级特性,比如:

  • 公平性、非公平性
  • 可重入特性
  • 超时机制
  • 响应中断
  • 共享、独享

举个例子,ReentrantLock就是一个具有很多高级特性的锁。从名字中就可以看出来这是一个带有重入功能的锁;ReentrantLock是独享锁;创建ReentrantLock可以通过参数指明是公平模式还是非公平模式,就可以实现公平锁或非公平锁;ReentrantLock#lockInterruptibly()和带时间参数的ReentrantLock#tryLock()可以响应中断。

从代码实现上来看,功能如此丰富的ReentrantLock代码实现居然不足两百行(去掉注释和空行),而大部分逻辑实现都是委托给AbstractQueuedSynchronizer。

那么问题来了,AbstractQueuedSynchronizer到底是什么呢?我们来探究一下。

AbstractQueuedSynchronizer设计与原理

首先来看一下继承结构:

aqs.png

  • AbstractOwnableSynchronizer仅实现的功能是维护了独占的线程。
  • 核心功能全部都是在AbstractQueuedSynchronizer实现。

然后,我们以具体的代码来深入分析AbstractQueuedSynchronizer的原理:

public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();
    try{
        lock.lock();//加锁操作
        // 业务处理

    }finally{
        lock.unlock();
    }
}

这里默认使用的是非公平锁,假设程序依次执行如下过程:线程一加锁、线程二加锁、线程一解锁、线程二解锁,ReentrantLock内部遍变化如下图所示:

aqs2.png

过程一:线程一t1加锁即调用lock(),如图1所示:

使用cas将state设置为1,代表加锁成功,同时将exclusiveOwnerThread设置为t1。线程一t1开始执行业务代码。

过程二:线程二t2来加锁即调用lock(),图2所示:

线程一:继续执行业务代码。 线程二:加锁失败,进入队列(首次会创建自身节点与Head节点),线程二处于休眠状态。

过程三:线程一t1解锁即调用unlock(),图3图4所示:

线程一:使用cas将state设置为0,代表解锁成功,同时将独占exclusiveOwnerThread设置为null。 线程二:被唤醒,继续执行代码。

线程二:被唤醒后尝试加锁,加锁成功,开始执行业务代码。 加锁逻辑会将头节点设置为当前节点(前一个头结点会被垃圾回收),并且将当前节点的thread设置为null;

过程四:线程二t2解锁即调用unlock(),如图5所示:

线程二:使用cas将state设置为0,代表解锁成功,同时将exclusiveOwnerThread设置为null。

总结一下

从以上的几个过程来看,ReentrantLock的lock()与unlock()核心都是操作其state状态和wait队列。

  • state:状态,比如是否持有锁或获取锁的次数。
  • wait队列:代表等待的线程,Head节点表示持有锁正在运行的线程。

如下图所示:

aqs3.png

从代码的实现来看,AbstractQueuedSynchronizer实现了绝大多数的逻辑。从加锁流程来看:这里使用了模板方法设计模式,尝试加锁的动作交给子类实现,后续的逻辑如创建队列Waiter节点、入队列、阻塞都是在AbstractQueuedSynchronizer中实现的;从解锁流程来看:这里同样使用了模板方法设计模式,尝试解锁的动作交给子类实现,后续逻辑如唤醒后继节点及后继节点设置头节点等都是在AbstractQueuedSynchronizer中实现。

特性的实现原理

公平性

主要体现在子类尝试加锁的过程,即tryAcquire()方法实现:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

这是非公平的尝试加锁逻辑:不管队列有没有排队,直接使用cas尝试修改state即加锁。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

这是公平的尝试加锁逻辑:认为队列的优先级更高,只有在队列没有线程排队的情况之下才会去尝试加锁,否则直接返回尝试失败,进行后续逻辑。

可重入性

每一次执行lock()方法,state的值就会递增1,解锁流程同样会递减1,直至减为0代表解锁。

超时机制

主要逻辑:尝试加锁失败,如果在截止时间之前被唤醒并获得锁,则加锁成功;否则,休眠固定时间后自动苏醒,并根据截止时间返回即加锁失败。

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();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

响应中断

注意:并不是所有的方法都能响应中断、并不是立刻能响应的。

共享锁、独占锁

独占锁只能有一个线程持有锁,如ReentrantLock。 共享锁可以有多个线程持有锁,CountDownLatch。 如CountDownLatch,初始化state值代表有几个线程共享,每个线程执行完都会尝试释放锁,即state递减1,当state减为0的时候,会唤醒队列中阻塞的线程。调用awit()方法的线程,如果此时state不为0就会阻塞在队列中。