队列同步器详解

队列同步器介绍 

队列同步器AbstractQueuedSynchronizer,是用来构建锁或者其余同步组件的基础框架,它使用了一个int成员变量表示同步状态,经过内置的FIFO队列来完成资源获取线程的排队工做。node

  同步器的主要使用方式是继承,通常做为同步器组件的静态内部类,在同步器中仅定义了与状态相关的方法,且状态既能够独占获取也能够共享获取,这样就能够实现不一样的同步组件(ReetrantLock、CountDownLatch等)。同步组件利用同步器进行锁的实现,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等顶层操做。  同步器的设计是基于模板方法模式的,使用者须要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
安全

同步器提供了3个方法来修改和访问同步状态:
数据结构

一、getState():获取当前同步状态并发

二、setState(int newState):设置当前同步状态框架

三、compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能保证操做的原子性。ui

队列同步器的实现

下面主要从实现角度分析同步器是如何完成线程同步的,主要包括:同步队列、独占式同步状态的获取和释放、共享式同步状态的获取和释放等核心数据结构与模板方法。spa

一、同步队列

同步器利用同步队列来完成同步状态的管理。它是一个FIFO的双向队列,当线程获取状态失败时,同步器会将当前线程和等待状态等信息包装成尾节点放入同步队列中,同时会阻塞当前线程。当同步状态释放时,会唤醒首节点,使其尝试获取同步状态。线程

在节点加入过程当中,会涉及到并发的问题,因此这个加入过程要确保线程安全,所以同步器提供了一个基于CAS设置尾节点的方法:compareAndSetTail(Node expect, Node update)。设计

在设置首节点过程当中,首节点是经过获取同步状态成功的线程设置的,因为只有一个线程可以获取到同步状态,因此设置首节点的方法并不须要使用CAS来保证。只须要将首节点设置原首节点的后续节点并断开原节点的next引用便可。code

二、独占式同步状态的获取

同步器是经过acquire()方法来获取同步状态的,该方法是模板方法:

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

一、调用自定义同步器实现的tryAcquire(arg)获取同步状态,该方法保证线程安全。

二、若是获取同步状态失败,则构造同步节点(独占式),经过addWaiter方法放入同步队列的尾部

final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

三、节点在加入同步队列后,在同步队列中全部的节点都处于自旋状态,可是只有前驱节点是头节点才能尝试获取同步状态。一是由于头节点是已经获取到同步状态的节点,当头节点的线程释放同步状态以后,将会唤醒后继节点,后继节点被换唤醒后须要检查本身的前驱节点是不是头节点。而是由于这样处理能够维护同步队列的FIFO原则。

三、独占式同步状态的释放

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

该方法执行时,会唤醒节点的后继节点线程,经过调用unparkSuccessor(h)方法来唤醒处于等待状态的线程。

四、共享式同步状态获取

与独占式的区别在于:在同一时刻可否有多个线程同时获取到同步状态。例如文件读写,在同一时刻,若是进行读操做,那么写操做会被阻塞,可是能够同时有多个读操做,这种读操做就是共享式的。相反,写操做只能有一个,而且阻塞其余全部的写操做和读操做。

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 (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

一、调用tryAcquireShared(arg)尝试获取同步状态,若是方法值大于等于0,则表示获取同步状态成功。

二、在方法doAcquireShared自旋过程当中,若是前驱节点为头节点时,尝试获取同步状态。自旋结束的条件就是tryAcquireShared方法的返回值大于等于0.

五、共享式同步状态释放

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

这个与独占式相似,都是释放后将会唤醒处于等待状态的节点。惟一的区别是这个方法必须支持并发,由于释放同步状态的操做会来自多个线程。因此tryReleaseShared(arg)是经过CAS保证的。

相关文章
相关标签/搜索