JDK1.8源码解读之Semaphore

236 阅读14分钟

前言

  • 计数信号量。
  • 从概念上讲,信号量维护一组许可证。
  • 如有必要,每个{@link #acquire}都会阻止,直到获得许可为止,然后获得许可。
  • 每个{@link #release}都会添加一个许可证,从而有可能释放阻塞的获取者。
  • 但是,没有使用实际的许可对象。
  • {@code Semaphore}仅保留可用数量的计数并采取相应措施。
  • 与无法访问某些(物理或逻辑)资源相比,信号量通常用于限制线程数。
  • 在获取项目之前,每个线程必须从信号灯获取许可,以确保可以使用该项目。
  • 当线程完成该项目后,它将返回到池中,并向信号量返回一个许可,从而允许另一个线程来获取该项目。
  • 请注意,在调用{@link #acquire}时不会保留任何同步锁,因为这将阻止将某个项目返回到池中。
  • 信号量封装了限制访问池所需的同步,与维护池本身的一致性所需的任何同步分开。
  • 初始化为一个的信号灯可以用作互斥锁,该信号灯最多只能使用一个许可。
  • 这通常被称为二进制信号量,因为它只有两个状态:一个许可可用,或者零许可可用。
  • 当以这种方式使用时,二进制信号量具有属性(与许多{@link java.util.concurrent.locks.Lock}实现不同),该“锁”可以由所有者以外的线程释放(因为信号量具有没有所有权的概念)。
  • 这在某些特殊情况下(例如死锁恢复)很有用。
  • 此类的构造函数可以选择接受公平参数。
  • 设置为false时,此类不保证线程获取许可的顺序。
  • 特别是允许插入,即可以在正在等待的线程之前为调用{@link #acquire}的线程分配一个许可-从逻辑上讲,新线程将自己置于等待线程队列的开头。
  • 当公平性设置为true时,信号量可确保选择调用任何{@link #acquire()获取}方法的线程以处理它们调用这些方法的顺序来获得许可(先进先出; FIFO)。
  • 请注意,FIFO排序必定适用于这些方法中的特定内部执行点。
  • 因此,一个线程有可能在另一个线程之前调用{@code acquisition},但在另一个线程之后到达排序点,并且类似地从该方法返回时也是如此。
  • 另请注意,未计时的{@link #tryAcquire()tryAcquire}方法不遵循公平性设置,但会采用任何可用的许可。
  • 通常,用于控制资源访问的信号量应初始化为公平,以确保没有线程因访问资源而挨饿。
  • 当使用信号量进行其他类型的同步控制时,非公平排序的吞吐量优势通常会超过公平考虑。
  • 此类还提供了方便的方法来同时{@link #acquire(int)获取}和{@link #release(int)发布}多个许可。
  • 当在不公平的情况下使用这些方法时,请注意无限期推迟的风险增加。
  • 内存一致性影响:在调用“发布”方法(例如{@code release()})之前在线程中执行的操作在成功“获取”方法之后进行的事前操作例如另一个线程中的{@codequiren()}。

源码

package java.util.concurrent;

public class Semaphore implements java.io.Serializable {
    private static final long serialVersionUID = -3222578661600680210L;
    /** All mechanics via AbstractQueuedSynchronizer subclass */
    private final Sync sync;

    /**
     * 信号量的同步实现。使用AQS状态代表许可。分为公平和非公平版本。
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

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

        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

    /**
     * NonFair version
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

    /**
     * Fair version
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

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

    /**
     * 创建具有给定数量的许可和不公平设置的{@code Semaphore}。
     */
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    /**
     * 创建具有给定数量的许可和给定的公平性的{@code Semaphore}。
     */
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

    /**
     * 从此信号灯获取许可,直到一个可用的信号被阻塞,或者线程被{@linkplain Thread#interrupt interrupted}阻塞。
     * 如果有许可证,则获取许可证并立即返回,从而将可用许可证的数量减少一个。
     * 如果没有可用的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到发生以下两种情况之一:另一个线程为此信号量调用{@link #release}方法,并且下一步将为当前线程分配一个线程。
     * 允许;或某个其他线程{@linkplain Thread#interrupt interrupts}当前线程。
     * 如果当前线程:在进入此方法时设置了其中断状态;或者在等待许可时{@linkplain Thread#interrupt被中断},则抛出{@link InterruptedException}并清除当前线程的中断状态。
     */
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * 从此信号量获取许可,直到可用为止。
     * 如果有许可证,则获取许可证并立即返回,从而将可用许可证的数量减少一个。
     * 如果没有可用的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到某个其他线程为此信号量调用{@link #release}方法,然后再为当前线程分配许可。
     * 如果当前线程在等待许可时被{@linkplain Thread#interrupt interrupted中断},则它将继续等待,但是与没有许可的情况相比,分配该线程许可的时间可能会有所变化。
     * 发生中断。
     * 当线程确实从该方法返回时,将设置其中断状态。
     *
     */
    public void acquireUninterruptibly() {
        sync.acquireShared(1);
    }

    /**
     * 仅在调用时可用时,才从此信号量获取许可。
     * 如果许可证可用,则获取许可证并立即返回,其值为{@code true},从而将可用许可证数量减少一个。
     * 如果没有可用的许可,则此方法将立即返回值{@code false}。
     * 即使已将此信号量设置为使用公平的排序策略,如果可用,则对{@code tryAcquire()}的调用将立即获得许可,无论当前是否正在等待其他线程。
     * 即使破坏公平性,这种“讨价还价”的行为在某些情况下还是有用的。
     * 如果要遵守公平性设置,请使用几乎等效的{@link #tryAcquire(long,TimeUnit)tryAcquire(0,TimeUnit.SECONDS)}(它还会检测到中断)。
     */
    public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }

    /**
     * 如果在给定的等待时间内有一个可用信号并且当前线程尚未{@linkplain Thread#interrupt interrupted},则从此信号量获取许可。
     * 如果许可证可用,则获取许可证并立即返回,其值为{@code true},从而将可用许可证数量减少一个。
     * 如果没有可用的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到发生以下三种情况之一:另一个线程为此信号量调用{@link #release}方法,并且下一步将为当前线程分配一个线程。
     * 允许;或其他某个线程{@linkplain Thread#interrupt interrupts}当前线程;或经过了指定的等待时间。
     * 如果获得许可,则返回值{@code true}。
     * 如果当前线程:在进入此方法时设置了其中断状态;或在等待获取许可时{@linkplain线程#interrupt被中断},则抛出{@link InterruptedException}并清除当前线程的中断状态。
     * 如果经过了指定的等待时间,则返回值{@code false}。
     * 如果时间小于或等于零,则该方法将根本不等待。
     */
    public boolean tryAcquire(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * 释放许可证,将其返回到信号灯。
     * 发放许可证,将可用许可证数量增加一个。
     * 如果有任何线程试图获取许可,则选择一个线程并授予刚刚释放的许可。
     * 出于线程调度目的而启用(重新)该线程。
     * 不要求释放许可证的线程必须通过调用{@link #acquire}获得该许可证。
     * 通过在应用程序中编程约定,可以正确使用信号量。
     *
     */
    public void release() {
        sync.releaseShared(1);
    }

    /**
     * 从该信号量获取给定数量的许可,阻塞直到所有许可都可用,或者线程{@linkplain Thread#interrupt interrupted}。
     * 如果有可用许可证,则获取给定数量的许可证,然后立即返回,从而将可用许可证的数量减少给定数量。
     * 如果没有足够的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到发生以下两种情况之一:另一个线程为此信号量调用{@link #release()release}方法之一,当前线程为接下来分配许可证,可用许可证的数量满足此请求;或某个其他线程{@linkplain Thread#interrupt interrupts}当前线程。
     * 如果当前线程:在进入此方法时设置了其中断状态;或者在等待许可时{@linkplain Thread#interrupt被中断},则抛出{@link InterruptedException}并清除当前线程的中断状态。
     * 相反,将要分配给该线程的所有许可都分配给其他尝试获取许可的线程,就像通过调用{@link #release()}使许可可用一样。
     *
     */
    public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }

    /**
     * 从此信号量获取给定数量的许可,直到所有条件都可用为止都将阻塞。
     * 如果有可用许可证,则获取给定数量的许可证,然后立即返回,从而将可用许可证的数量减少给定数量。
     * 如果没有足够的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到某个其他线程为此信号量调用{@link #release()release}方法之一为止,接下来将为当前线程分配许可,并可使用的许可证数量满足此要求。
     * 如果当前线程在等待许可时{@linkplain线程#interrupt被中断},则它将继续等待,并且其在队列中的位置不受影响。
     * 当线程确实从该方法返回时,将设置其中断状态。
     *
     */
    public void acquireUninterruptibly(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireShared(permits);
    }

    /**
     * 仅在调用时所有可用的条件下,从此信号量获取给定数量的许可。
     * 如果有可用许可证,则获取给定数量的许可证,并立即返回,其值为{@code true},从而将可用许可证的数量减少给定数量。
     * 如果没有足够的许可证,则此方法将立即返回{@code false},并且可用许可证的数量不变。
     * 即使已将此信号量设置为使用公平的排序策略,如果有可用的线程,无论是否正在等待其他线程,对{@code tryAcquire}的调用都将立即获得许可。
     * 即使破坏公平性,这种“讨价还价”的行为在某些情况下还是有用的。
     * 如果要遵守公平性设置,请使用几乎等效的{@link #tryAcquire(int,long,TimeUnit)tryAcquire(permits,0,TimeUnit.SECONDS)}(它还会检测到中断)。
     *
     */
    public boolean tryAcquire(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.nonfairTryAcquireShared(permits) >= 0;
    }

    /**
     * 如果所有信号都在给定的等待时间内可用,并且当前线程尚未{@linkplain线程#interrupt中断},请从此信号量获取给定数量的许可。
     * 如果有可用许可证,则获取给定数量的许可证,然后立即返回,其值为{@code true},从而将可用许可证的数量减少给定数量。
     * 如果没有足够的许可,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到发生以下三种情况之一:其他一些线程为此信号量调用{@link #release()release}方法之一,当前线程为接下来分配许可证,可用许可证的数量满足此请求;或其他某个线程{@linkplain Thread#interrupt interrupts}当前线程;或经过了指定的等待时间。
     * 如果获得许可,则返回值{@code true}。
     * 如果当前线程:在进入此方法时设置了其中断状态;或在等待获取许可的过程中被{@linkplain Thread#interrupt interrupted中断},则抛出{@link InterruptedException}并清除当前线程的中断状态。
     * 相反,将要分配给该线程的所有许可,都分配给其他尝试获取许可的线程,就像通过调用{@link #release()}使许可可用一样。
     * 如果经过了指定的等待时间,则返回值{@code false}。
     * 如果时间小于或等于零,则该方法将根本不等待。
     * 相反,将要分配给该线程的所有许可,都分配给其他尝试获取许可的线程,就像通过调用{@link #release()}使许可可用一样。
     *
     */
    public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
        throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
    }

    /**
     * @param permits the number of permits to release
     * @throws IllegalArgumentException if {@code permits} is negative
     * 释放给定数量的许可证,将其返回到信号灯。
     * 释放给定数量的许可证,将可用许可证的数量增加该数量。
     * 如果有任何线程试图获取许可,则选择一个线程并给出刚刚释放的许可。
     * 如果可用许可的数量满足该线程的请求,则出于线程调度目的而(重新)启用该线程。
     * 否则,线程将等待,直到有足够的许可可用为止。
     * 如果在满足该线程的请求之后仍然有可用的许可,则将这些许可依次分配给其他尝试获取许可的线程。
     * 无需要求释放许可的线程必须通过调用{@link Semaphore#acquire acquisition}获得许可。
     * 通过在应用程序中编程约定,可以正确使用信号量。
     *
     */
    public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }

    /**
     * 返回此信号量中当前可用的许可数量。此方法通常用于调试和测试目的。
     */
    public int availablePermits() {
        return sync.getPermits();
    }

    /**
     * 获取并返回所有立即可用的许可证。
     */
    public int drainPermits() {
        return sync.drainPermits();
    }

    /**
     * 通过指示的减少量缩小可用许可证的数量。此方法在使用信号量跟踪变得不可用的资源的子类中很有用。
     * 此方法与{@code acquisition}的不同之处在于,它不会阻止等待许可证可用。
     */
    protected void reducePermits(int reduction) {
        if (reduction < 0) throw new IllegalArgumentException();
        sync.reducePermits(reduction);
    }

    /**
     * Returns {@code true} if this semaphore has fairness set true.
     *
     * @return {@code true} if this semaphore has fairness set true
     */
    public boolean isFair() {
        return sync instanceof FairSync;
    }

    /**
     * 查询是否有任何线程正在等待获取。
     * 请注意,由于取消可能随时发生,因此{@code true}返回值不能保证任何其他线程都可以获取。
     * 此方法主要设计用于监视系统状态。
     *
     * @return {@code true} if there may be other threads waiting to
     *         acquire the lock
     */
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    /**
     * 返回等待获取的线程数的估计值。
     * 该值只是一个估计值,因为在此方法遍历内部数据结构时,线程数可能会动态变化。
     * 此方法设计用于监视系统状态,而不用于同步控制。
     *
     * @return the estimated number of threads waiting for this lock
     */
    public final int getQueueLength() {
        return sync.getQueueLength();
    }

    /**
     * 返回一个包含可能正在等待获取的线程的集合。
     * 因为实际的线程集在构造此结果时可能会动态变化,所以返回的集合只是尽力而为的估计。
     * 返回的集合的元素没有特定的顺序。
     * 设计此方法是为了便于构造子类,以提供更广泛的监视功能。
     *
     */
    protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }

    /**
     * 返回标识此信号量及其状态的字符串。括号中的状态包括字符串{@code“ Permits =”},后跟许可数量。 
     * @return a string identifying this semaphore, as well as its state
     */
    public String toString() {
        return super.toString() + "[Permits = " + sync.getPermits() + "]";
    }
}