java锁的关键-队列同步器(AQS)

一、什么是AQS?AQS与锁的关系是怎么样?
AQS:AbstractQueuedSynchronizer,队列同步器(以下简称同步器),是用来构建锁或者其他同步组件的基础框架。它使用了一个int成员变量表示同步状态,通过内置的FIFO双向队列来完成线程获取资源的排队工作。AQS的两个核心:同步状态和同步队列。

AQS与锁的关系
同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。

可以这样来理解锁和同步器之间的关系:
a. 锁是面向使用者的。它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),锁对使用者隐藏了实现细节。
b. 同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理线程的排队等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所需关注的领域。

二、同步器的设计
同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,然后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

2.1 同步器提供了3个方法来访问或修改同步状态:

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

2.2 同步器提供了5个可重写的方法:

  1. 独占式获取同步状态方法。实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态。
protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
  1. 独占式释放同步状态方法。等待获取同步状态的线程将有机会获取同步状态
protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
  1. 共享式获取同步状态方法。返回>=0时表示获取成功,否则获取失败
protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
  1. 共享式释放同步状态方法
protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
  1. 判断是否在独占模式下被线程占用的方法。当前同步器是否在独占模式下被线程占用,用该方法表示是否被当前线程所独占。
protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }

2.3 同步器提供的模板方法:
模板方法一

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

独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回。否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法。

模板方法二

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

与acquire(int arg)方法相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,该方法会抛出InterruptedException。

模板方法三

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

在acquireInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,将会返回false,否则返回true。

模板方法四

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态。

模板方法五

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

与acquireShared(int arg)相同,该方法响应中断

模板方法六

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquireShared(arg) >= 0 ||
        doAcquireSharedNanos(arg, nanosTimeout);
}

在acquireSharedInterruptibly(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;
}

独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。

模板方法八

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

共享式的释放同步状态

模板方法九

public final Collection<Thread> getQueuedThreads() {
    ArrayList<Thread> list = new ArrayList<Thread>();
    for (Node p = tail; p != null; p = p.prev) {
        Thread t = p.thread;
        if (t != null)
            list.add(t);
    }
    return list;
}

获取等待在同步队列上的线程集合

三、队列同步器的实现分析

3.1 同步队列
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态待信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

Node内部类

static final class Node {

    static final Node SHARED = new Node();
    
    static final Node EXCLUSIVE = null;
    
    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;
    }
}

同步队列的基本结构:

在这里插入图片描述
同步器包含了两个节点类型的引用,一个指向头节点,一个指向尾节点。当一个线程成功获取了同步状态,其他线程将无法获取到同步状态,转而被构造成为节点加入到同步队列中,加入队列的过程必须要保证线程安全,因此同步器提供了一个基于CAS的设置尾节点的方法:

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

它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功了,当前节点才正式与之前的尾节点建立关联。

同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后续节点,而后续节点将会在获取同步状态成功时将自己设置为首节点。

设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法并不需要CAS来保证,它只需要将首节点设置成为原首节点的后继节点并断开原首节点的next引用即可。

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

独占式同步状态获取流程
在这里插入图片描述
通过调用同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感,也就是线程获取同步状态失败后进行同步队列中,后续对线程进行中断操作时,线程并不会从同步队列中移出。

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

上述代码中主要完成了同步状态的获取、节点构建、加入到同步队列和自旋等待的工作,源码分析如下:
tryAcquire(arg):尝试去获取同步状态,如果获取成功返回true,否则返回false。该方法是自定义同步器自己实现的方法,并且一定要保证线程安全。
addWaiter(Node.EXCLUSIVE):以独占的模式创建节点,并将节点添加到同步队列的尾部。

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

compareAndSetTail(pred, node)方法为确保节点能够被线程安全的添加到同步队列尾部。如果这里不保证线程安全,那么在一个线程获取到同步状态后,其它线程因为获取同步状态失败而并发的向同步队列中添加节点时,同步队列就不能保证数据的正确性了。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg):以自旋的方式获取同步状态。

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

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;
}
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

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

共享式获取与独占式获取的区别: 同一时刻能否有多个线程同时获取到同步状态。
以文件的读写为例:
1.如果有一个程序在读文件,那么这一时刻的写操作均被阻塞,而读操作能够同时进行。
2.如果有一个程序在写文件,那么这一时刻不管是其他的写操作还是读操作,均被阻塞。
3.写操作要求对资源的独占式访问,而读操作可以是共享式访问。

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;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

当前线程首先调用tryAcquireShared()可重写方法,共享式的获取同步状态。如果返回值大于等于0,表示获取成功并返回
如果返回值小于0表示获取失败,调用doAcquireShared()方法,让线程进入自旋状态。
自旋过程中,如果当前节点的前驱节点是头结点,且调用tryAcquireShared()方法返回值大于等于0,则退出自旋。否则,继续进行自旋。

共享式的释放同步状态

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

共享式释放同步状态,使得同步状态的释放可能同时来自多个线程。为了确保同步状态的安全释放,一般通过循环和CAS来保证。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值