AbstractQueuedSynchronizer原理剖析

队列同步器AbstractQueuedSynchronizer(简称同步器),主要是用于构建锁或其他同步组件(例如Semaphore)的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,成为实现大部分同步需求的基础。《Java并发编程的艺术》

上一篇介绍ReentrantLock可重入锁时提到其底层实现为同步器,其内部定义一个静态内部类继承AbstractQueuedSynchronizer,通过实现相关模版方法来完成线程同步锁的功能。

基础知识

同步器提供了3个方法(getState()、setState(int newState)、compareAndSetState(int expect, int update))来实现状态的获取的设置,上述方法通过硬件层面的原子操作保证其安全性。同时,同步器还提供了一系列的模版方法(例如:tryAcquire(int arg)、tryRelease(int arg)),其本身不具体实现。同步组件(锁、semaphore等)内部通过静态内部类继承同步器,实现相关的同步接口,完成线程同步功能。锁是面向用户的,其定义一系列接口,底层实现交给同步器完成。同步器所提供的模板方法可分为3类:独占式获取和释放同步状态、共享式获取和释放同步状态、查询同步队列的等待情况。

实现分析

1.同步队列

同步器内部通过一个同步队列(FIFO双向队列)来完成同步状态的管理,当线程获取同步状态失败时,会将线程和等待状态信息构造成一个Node对象,并加入到同步队列,同时会将当前线程阻塞。当同步状态释放时,会将头节点的后继节点线程唤醒,并修改头节点引用。
同步队列的基本结构
上图中同步器包含两个节点类型的引用,一个指向头节点,一个指向尾节点。头节点即为当前获取同步状态成功的节点,头节点线程释放同步状态后,会唤醒后继节点线程,当后继节点线程获取同步状态成功后会将当前线程对应的节点设置为头节点。因此设置头节点的操作只存在与当前获取同步状态成功的线程,不存在并发操作,不需要使用CAS操作来保证其安全性。不同的是,尾节点的设置需要使用CAS操作,原因就是多线程并发获取同步状态,要保证等待线程加入队列的安全性,只有
compareAndSetTail方法设置成功后,节点才回进入队列。

2.独占式同步状态获取与释放
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    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;
    }
    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;
                }
            }
        }
    }
    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);
        }
    }

调用同步器的acquire(int arg)方法可以获取同步状态,该方法不处理线程中断操作,当线程获取同步状态失败后加入同步队列后,对线程进行中断无法从队列中移除,只有通过前继节点的唤醒操作,直到获取同步状态。线程获取同步状态失败后,调用addWaiter(Node mode),将线程和等待信息构造的节点加入到同步队列中,enq(final Node node)方法是一个死循环,直到节点节点成功加入同步队列才会退出。当节点加入同步队列后调用acquireQueued(final Node node, int arg) 方法,该方法内部为一个自旋的死循环,会阻塞该线程,直到其前驱节点释放同步状态,唤醒该线程,且成功获取同步状态后才会返回。
独占式同步状态获取流程

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

线程获取到同步状态并执行完成后,通过release方法释放同步状态,释放成功后将唤醒头节点的后继节点线程。

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

共享式与同步式最大的区别在于同一时刻能否允许多个线程同时获取同步状态。

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        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();
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

共享式同步状态获取通过acquireShared方法,doAcquireShared方法实现为自旋操作,直到tryAcquireShared方法返回值大于0,表示成功获取到同步状态并退出自旋。共享式同步状态释放由于可能存在多个线程同时拥有同步状态,因此其在释放时必须保证线程安全,因此在doReleaseShared方法中会同过自旋和CAS操作实现释放。

4.独占式超时获取同步状态

通过同步器的doAcquireNanos(int arg, long nanosTimeout)方法可以实现超时获取同步状态,该方法与独占式最大的区别在于获取同步状态失败后通过一个超时时间进行自旋,在设定的超时时间内,若成功获取同步状态则成功返回,即使其前面有其他等待的线程,否则失败。除此之外,其流程基本上与独占式获取基本相同。

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);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L) {
                cancelAcquire(node);
                return false;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

独占式超时获取同步状态

结束语

本文介绍AbstractQueuedSynchronizer队列同步器的相关知识,包括使用和实现原理。通过了解同步器的实现,基本上就掌握了锁的实现原理。因此,了解同步器的的实现对于理解锁的实现机制非常重要。本篇文章内容大部分来自《Java并发编程艺术》,有兴趣的可以读下该书,好好看看源码。

猜你喜欢

转载自blog.csdn.net/hb_csu/article/details/80494046
今日推荐