并发编程之AQS

1.AQS是什么

AbstractQueuedSynchronizer
这是一个采用模板设计模式的抽象类,idea里面alt+7可以看到他的类结构,两个内部类Node,ConditionObject
public修饰并且有final修饰的都是模板方法,
protected修饰的没有final修饰的都抛出了一个UnsupportedOperationException();说明这些都是需要子类自己实现的流程方法
idea选中AbstractQueuedSynchronizer类ctrl+T我们看到并发包里不少类都用到了他,我们以ReentrantLock为例,这是一个可重入锁
他是用一个内部类Sync去继承的AbstractQueuedSynchronizer,内部类最大的作用就是解决了java单继承的问题(虽然这里不存在这个问题)


模板方法:
独占式获取
accquire()
acquireInterruptibly()
tryAcquireNanos()
共享式获取
acquireShared()
acquireSharedInterruptibly()
tryAcquireSharedNanos()
独占式释放锁
release()
共享式释放锁
releaseShared()

需要子类覆盖的流程方法
独占式获取 tryAcquire()
独占式释放 tryRelease()
共享式获取 tryAcquireShared()
共享式释放 tryReleaseShared()
这个同步器是否处于独占模式 isHeldExclusively()

AQS中的数据结构-节点和同步队列
竞争失败的线程会打包成Node放到同步队列,Node可能的状态里(waitStatus):
CANCELLED = 1:线程等待超时或者被中断了,需要从队列中移走
SIGNAL = -1:后续的节点等待状态,当前节点,通知后面的节点去运行
CONDITION = -2 :当前节点处于等待队列
PROPAGATE = -3:共享,表示状态要往后面的节点传播
0, 表示初始状态

static {
    try {
        stateOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        headOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
        tailOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        waitStatusOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("waitStatus"));
        nextOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("next"));

    } catch (Exception ex) { throw new Error(ex); }
}

通过上面这段静态代码块里我们可以知道,内部类Node里面维护了两个值,waitStatus和 next,外层维护了三个分别是state,头结点head,尾结点tail
我们从ReentrantLock实现的Lock里的 lock(),lockInterruptibly(),tryLock(),unlock(),newCondition();开始看起
lock()里由内部类的Sync实现,Sync又分了FairSync和NonFairSync,我们看看FairSync的lock实现

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

我们先不看模板方法里的acquireQueued(addWaiter(Node.EXCLUSIVE), arg),我们看看ReentrantLock里他重写的tryAcquire(1)

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;
}

分析上面的代码只做了两件事把维护的外层state使用CAS从0修改为1,当前拿到独占锁的线程exclusiveOwnerThread赋值为当前线程
一会再看hasQueuedPredecessors()做了什么事,如果拿到的state==0,那么久尝试CAS修改为1,成功了设置当先线程为exclusiveOwnerThread,如果state不为0,再判断当前线程是不是exclusiveOwnerThread,
如果是,就把state+=1,这里不用CAS去加是因为当前线程就是拿到独占锁的线程,state+=1就是实现重入锁的关键,解决了同一个线程里锁嵌套的问题;
我们回过头看看hasQueuedPredecessors();

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

我们从他维护的五个值里大概可以知道,AQS是把多个线程去争夺这个锁的时候,第一个进来的线程拿到锁后把state修改为>=1的值,然后exclusiveOwnerThread设置为自己,可能把自己封装一个Node放到head,然后第二个线程进来发现state是1
就把自己封装成Node,放到tail,tail.pre = head, head.next = tail,这样....后面来的线程也是封装成Node往后面加....这个只是我们的大致预感,这块的实现就是由acquire(ing arg)模板方法里后面的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)实现的.
继续看hasQueuedPredecessors(),这里就是判断头结点尾结点不相等,并且头结点的下一个为空,或者下一个节点的线程不是本线程,就是说链表里不止一个node并且头结点的下一个为空或者头结点的下一个不是本线程?大概就是判断链表里面有没有排队的线程
假设有就不会直接去抢着去拿这个锁,而直接返回发false了,这里我们看的是FairSync的lock实现里面的,我们对比下NonFairSync的lock实现.

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;
}

对比发现就是少了hasQueuedPredecessors()的判断
所以我们知道,公平锁就是我一个新线程去去试图拿锁的时候,需要去检测AQS的链表里面有没有排队的线程,如果有,自己就老老实实去排队,不去在这个时候去抢这个锁(state置为1,exclusiveOwnerThread设置成自己线程);
而非公平锁的就不去检测有没有其他线程排队,只要发现现在我拿到的状态是0,我就先去抢1,抢不到去求返回false了老老实实去排队,抢到了就直接拿去用了

ok,假设我们这里没有拿到锁(cas修改1失败),返回了fasle,我们看看模板方法里的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

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;
}

先看addWaiter(null),这里就是刚刚说的封装一个Node,node.nextWaiter=null,node.thread = thread;拿到维护的尾结点tail,尝试把node加成尾结点,成功了就返回node,不然就进入enq(node);

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

这个就是经典的CAS自旋锁,上一篇的并发编程CAS里实现线程安全的自增就是类似,这里去保证线程安全的加入尾结点成功;
我们接着看acquireQueued(tail), 1))

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);
    }
}

这里自旋里判断刚刚排队的tail,他的前节点是不是head节点,如果是的,他就去尝试拿锁tryAcquire(1),成功了就把自己变成head,然后返回false;
前节点不是头结点或者拿锁失败了,先看看shouldParkAfterFailedAcquire(p, node)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

这里其实可以理解成后面&& parkAndCheckInterrupt()的执行条件,parkAndCheckInterrupt()里面就是LockSupport.park(this);
这个LockSupport.park(this)我们在并发编程之线程创建的方式和常用方法里使用过,就是让当前的线程挂起;
这个方法里有返回值,return Thread.interrupted();这个感觉应该是另一个带interrupt的方法要用到的.
我们前面说过竞争失败的线程会打包成Node放到同步队列,Node可能的状态里(waitStatus):
CANCELLED = 1:线程等待超时或者被中断了,需要从队列中移走
SIGNAL = -1:后续的节点等待状态,当前节点,通知后面的节点去运行
CONDITION = -2:当前节点处于等待队列
PROPAGATE = -3:共享,表示状态要往后面的节点传播
0, 表示初始状态
这里,node的前节点如果waitStatus是SIGNAL,就直接返回true,让node进入挂起,如果前节点是CANCELLED,就需要从链表里面去除掉他,就有了node.prev = pred = pred.prev;,
就是把node的前节点指向了前节点的前节点最后返回false然后接着自旋重新尝试拿锁去;
至此lock()结束


unlock();

再来看看unlock();

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

算了,不写了,unlock就是把state-=1;如果减到0了,说明该线程要释放锁了,没减到0就是该线程重入锁后面还需要被释放,减到0后获取头结点head,head不为空并且waitStatus != 0就 unparkSuccessor(h);
这个方法里就是先把head的waitStatus置为初始状态0,然后找head的next节点,如果下一个节点为null或者是waitState是CANCELLED状态,就从tail开始往前找到最前面waitState不是CANCELLED的节点,把这个节点唤醒
unlock()结束


ConditionObject

上面的lock和unlock操作的是AQS里维护的head和tail组成的链表,我们叫它AQS里的同步队列
AQS里ConditionObject的await和signal则是AQS下Node里维护的firstWaiter,lastWaiter组成的链表,我们叫它AQS里的等待队列,我们可以new多个ConditionObjcet来创建多个等待队里
ConditionObjcet也会去操作同步队列里的数据,await的时候把当前线程封装成一个Node放到等待队列里,然后把同步队列里的head(当前拿到锁的node)释放锁,然后挂起并唤醒同步队列里的下一个node里的线程(就是unlock里的tryRelease()操作);
signal()则是把等待队列里的firstWaiter添加到同步队列里的尾部(enq(final Node node)操作)等待参与锁的竞争

await()

我们再看看ConditionObject,主要看await(),和signal()方法,类似于类似于object.wait()和object.notify();
我们先看await()方法
把当前线程封装一个Node赋值给ConditionObject的lastWaiter,如果是第一个也会赋值firstWaiter,然后挂起该线程

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);//释放当前的锁
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {//判断当前节点是否已经在同步队列中,如果是则退出循环,如果不是就阻塞当前线程
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

isOnSyncQueue(node))是我们后面这个线程去执行acquireQueued(node, savedState)重新竞争锁的关键,这个是收到其他线程的signal()之后放入的
我们看看signal()方法,主要看doSignal();

signal()
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)//把firstWaiter指向往后移
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&   //把firstWaiter的node加到同步队列里去
             (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);//cas保证加入同步队列尾部
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

这个只是大致的浏览了下源码,并没有分析那么透彻,好多地方并没有去深究为什么要这样写这行代码有什么意义,仅仅是一边看看源码,一边总结的粗糙的理解,最后找个图看看这两个队列的操作


并发编程之AQS_第1张图片
image.png

await()方法


并发编程之AQS_第2张图片
image.png

signal()方法


并发编程之AQS_第3张图片
image.png

关于AQS模板方法里的共享式获取acquireShared()和共享式释放锁releaseShared()在ReentrantLock的Sync里并没有重写tryAcquireShared(),共享锁指的是多个线程可以去拿这把锁,如果没有控制拿锁线程的数量其实可以理解为没有锁
共享锁在ReentrantReadWriteLock里的读锁里有体现,拿锁线程数量是static final int MAX_COUNT = (1 << 16) – 1;


总结:公平锁非公平锁就是在新的线程进来去拿锁的时候,是否去检测同步队列有没有排队的一步操作,去检测了然后去排队就是公平,不检测直接尝试拿锁就是非公平锁

你可能感兴趣的:(并发编程之AQS)