并发-AQS之Semaphore源码解读

77 阅读5分钟

Semaphore是Java中一个同步工具类,用于控制同时访问某个资源的线程数。

整体架构

image.png

构造函数

Semaphore支持公平锁和非公平锁,默认非公平锁 构造函数如下

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

FairSync和NonfairSync是公平锁和非公平锁的实现,两者的区别在于获取共享资源的实现不一致。
NonfairSync的实现如下

protected int tryAcquireShared(int acquires) {
    //父类Sync中的实现
    return nonfairTryAcquireShared(acquires);
}

nonfairTryAcquireShared是一个非公平的尝试获取共享许可的方法。它的作用是尝试获取acquires个共享许可,返回当前剩余的许可数量

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        //如果<0直接返回,或者利用CAS将state设置成剩余许可证数量
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

FairSync实现

protected int tryAcquireShared(int acquires) {
    for (;;) {
        //与分公平锁不一样地方
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

第4行和分公平锁不一样hasQueuedPredecessors实现如下

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    //如果当前线程在AQS的等待队列中有前驱节点,则该方法返回true;当前节点就是前驱节点或者当前节点为空否则返回false。
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

获取许可

着重分析获取一个许可acquire(),其他主要介绍不同之处

acquire() 源码分析

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

acquireSharedInterruptibly是AQS中方法,共享方式获取,遇到其他线程中断当前线程返回InterruptedException,获取到许可直接返回,获取不到等待

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //第4行判断的同时会清除中断标记
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
    // 获取不到许可,添加队列并等待
        doAcquireSharedInterruptibly(arg);
}

继续doAcquireSharedInterruptibly,这个方法是实现共享模式下的可中断获取同步许可,如果当前线程中断,将会抛出中断状态

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //1.添加共享模式Node节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            //2.获取前驱节点
            final Node p = node.predecessor();
            //如果是头节点
            if (p == head) {
                //3.尝试获取许可
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //4.把新增的节点设置头结点,并换新后面节点
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //5.获取失败判断是否需要挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                //6.挂起检查是否中断
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            //7.失败取消获取许可
            cancelAcquire(node);
    }
}

先看addWaiter,主要是新增一个Node节点,并放到尾结点,并替换当前的tail

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;
        }
    }
    // 同步队列为空或者上述CAS设置失败,入队
    enq(node);
    return node;
}
//入队操作,包括初始化双向队列
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            //注意头结点head是一个无含义的哨兵节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

tryAcquireShared方法前面已经介绍,需要继承AQS的同步类进行实现 setHeadAndPropagate是在当前节点已经获取许可后,剩余的许可r仍然大于0,尝试唤醒后面节点

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    //设置头结点
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            //唤醒头结点
            doReleaseShared();
    }
}
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     //设置成PROPAGATE状态,表示需要在之后继续传播释放信号
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    //s节点可能已经取消CANCELLED
    if (s == null || s.waitStatus > 0) {
        s = null;
        //从尾结点开始向前找第一个SIGNAL PROPAGATE节点进行唤醒
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

shouldParkAfterFailedAcquire获取失败判断是否需要挂起,源码解释已经很详细,主要是前置节点pred如果是SINGAL状态,则当前线程可以park;如果是取消状态,需要将取消队列移除,并重新连接队列,doAcquireSharedInterruptibly下一次for循环进行判断;如果是PROPAGATE或者为0,需要设置为SINGAL,下次for就可以park

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

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    //唤醒后判断中断标记
    return Thread.interrupted();
}

失败需要取消许可,目前failed为true可能出现在中断异常

if (failed)
    cancelAcquire(node);

cancelAcquire主要是将node节点设置为CANCELD,然后从同步队列中移除

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

其他获取许可方法

不中断获取一个许可acquireUninterruptibly

public void acquireUninterruptibly() {
    sync.acquireShared(1);
}
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);
    }
}

tryAcquire不等待,直接返回标记

public boolean tryAcquire() {
    return sync.nonfairTryAcquireShared(1) >= 0;
}

tryAcquire(long timeout, TimeUnit unit)

等待一段时间返回是否获取标记

public boolean tryAcquire(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquireShared(arg) >= 0 ||
        doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                //唤醒后获取许可失败,直接返回false
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                //时间很短,直接for循环,线程状态切换消耗大于循环
                nanosTimeout > spinForTimeoutThreshold)
                //线程等待一段时间
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

加锁流程

image.png 比较懒,直接取自与 blog.csdn.net/weixin_4631…

释放许可

release,整体比较简单,直接贴源码

public void release() {
    sync.releaseShared(1);
}
public void release(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.releaseShared(permits);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    //Semaphore中应该走不到
    return false;
}
//Sync中实现
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

doReleaseShared上面已经贴过源码,不再赘述

解锁流程

image.png 直接取自与 blog.csdn.net/weixin_4631…

其他方法

public int availablePermits() 获取当前许可数量
public int drainPermits() 用于获取并重置当前 Semaphore 对象的可用许可数

JDK中实现AQS简介

同步工具与AQS关联详细介绍
AQS原理讲解AQS原理介绍并发-AQS原理讲解
ReentrantLock使用AQS保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock记录当前获得锁的线程标识,用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理。AQS之Reentrantlonk源码解读
Semaphore使用AQS同步状态来保存信号量的当前计数。tryRelease会增加计数,acquireShared会减少计数。Semaphore 源码分析以及AQS共享加解锁
CountDownLatch在多线程并发执行任务时,有时需要让某些线程等待某些条件达成后再开始执行,这时就可以使用CountDownLatch来实现CountDownLatch 源码分析
ThreadPoolExecutor创建线程池中的工作线程worker继承AQS,实现独占资源参考 并发-AQS之ThreadPoolExecutor源码解读(一)
CyclicBarrier多个线程等待彼此达到一个共同的屏障点,然后同时继续执行。并发-AQS之CyclicBarrier源码解读
ReentrantReadWriteLock可重入读写锁,它允许多个线程同时读取一个共享资源,但只允许一个线程写入该共享资源。参考 并发-AQS之ReentrantReadWriteLock源码解读(一)