Semaphore源码解析

Semaphore源码解析

描述:

一个计数信号量。从概念上讲,信号量维护一组许可。每个acquire() 方法在必要时阻塞,直到获得许可,然后才能使用它。每次 release() 释放一个许可,潜在地释放一个阻塞获取者。但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。

信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。例如,下面是一个使用信号量来控制对项目池的访问的类:

class Pool {
  private static final int MAX_AVAILABLE = 100;
  private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
  public Object getItem() throws InterruptedException {
    available.acquire();
    return getNextAvailableItem();
  }
  public void putItem(Object x) {
    if (markAsUnused(x))
      available.release();
  }
  
  // 不是一个特别有效的数据结构;只是为了演示
 protected Object[] items = ... 被管理的任何类型的项目
 protected boolean[] used = new boolean[MAX_AVAILABLE];
 protected synchronized Object getNextAvailableItem() {
   for (int i = 0; i < MAX_AVAILABLE; ++i) {
     if (!used[i]) {
        used[i] = true;
        return items[i];
     }
   }
   return null; // not reached
 }
 protected synchronized boolean markAsUnused(Object item) {
   for (int i = 0; i < MAX_AVAILABLE; ++i) {
     if (item == items[i]) {
        if (used[i]) {
          used[i] = false;
          return true;
        } else
          return false;
     }
   }
   return false;
 }
}

在获取一个项目之前,每个线程必须从信号量获得一个许可,以保证项目可以使用。当线程处理完项目后,将其返回到池中,并向信号量返回一个许可,允许另一个线程获取该项目。注意,当调用 acquire() 时,不会持有同步锁,因为这将阻止某个项目返回到池中。信号量封装了限制对池的访问所需的同步,与维护池本身一致性所需的同步分开。

初始化为1的信号量,使用时最多只有一个许可,可以作为互斥锁。这通常被称为 二进制信号量 ,因为它只有两种状态:一个许可证可用,或者零许可证可用。当以这种方式使用时,二进制信号量具有属性(与许多java.util.concurrent.locks Lock 的实现不同),那么" Lock "可以由线程所有者以外的线程释放(因为信号量没有所有权的概念)。这在一些特殊的上下文中很有用,比如死锁恢复。

该类的构造函数可选地接受一个公平性形参。当设置为false时,该类不保证线程获得许可的顺序。特别是,闯入是允许的,也就是说,调用 acquire 的线程可以在已经等待的线程之前分配一个许可 - - 逻辑上,新线程将自己置于等待线程队列的头部。当公平性设置为true时,信号量保证调用 acquire() 方法的线程,会被选择以按照它们调用这些方法的顺序获得许可(先进先出;FIFO)。请注意,FIFO 排序必须应用于这些方法中的特定内部执行点。因此,一个线程可以在另一个线程之前调用 acquire ,但在另一个线程之后到达排序点,从方法返回时也是类似的情况。还要注意,未计时的 tryAcquire() 方法不遵守公平性设置,而是接受任何可用的许可。

通常,用于控制资源访问的信号量应该初始化为公平,以确保没有线程因访问资源而饿死。当将信号量用于其他类型的同步控制时,非公平排序的吞吐量优势通常超过公平性考虑。

这个类还提供了方便的方法来 acquire(int) 和 release(int) 一次释放多个许可证。当没有公平地使用这些方法时,请注意增加无限期延期的风险。

内存一致性影响:线程中调用 “release” 方法之前的操作。例如 release() happen-before acquire

源码:

public class Semaphore implements java.io.Serializable {
    private static final long serialVersionUID = -3222578661600680210L;
    
    /** 所有机制通过AbstractQueuedSynchronizer子类 */
    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;
            }
        }

        /**
         * 清空许可(许可证数置为0)
         */        
        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

    /**
     * 非公平版本
     */
    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);
        }
    }

    /**
     * 公平版本
     */
    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;
            }
        }
    }

    /**
     * 构造函数:指定许可证个数,采用非公平版本
     */
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    /**
     * 构造函数:指定许可证个数,指定是否公平版本
     */
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

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

    /**
     * 从这个信号量获取一个许可,阻塞直到一个可用。
     *
     * 1.获取一个许可证(如果有)并立即返回,将可用许可证的数量减少一个。
     * 2.如果没有可用的许可,则当前线程出于线程调度的目的被禁用,并处于休眠状态,直到其他线程调用这个信号量的 release 方法,当前线程下一个将被分配一个许可。
     * 3.如果当前线程在等待许可时被中断,那么它将继续等待,但与未发生中断时线程收到许可证的时间相比,分配给线程的许可证时间可能会发生变化。当线程从这个方法返回时,它的中断状态将被设置。
     */
    public void acquireUninterruptibly() {
        sync.acquireShared(1);
    }

    /**
     * 仅当调用时可用时,才从此信号量获取许可。
     *
     * 1.获取一个许可(如果有)并立即返回,值为  true ,将可用的许可数量减少1。
     * 2.如果没有可用的许可,则该方法将立即返回值  false 。
     * 3.即使这个信号量被设置为使用公平排序策略,如果有一个许可可用,调用 tryAcquire() 将立即获得一个许可,无论当前是否有其他线程正在等待。
     *
     * 这个“闯入“ 行为在某些情况下是有用的,即使它破坏了公平。
     * 如果你想遵守公平性设置,那么使用  tryAcquire(long, TimeUnit) tryAcquire(0, TimeUnit. seconds),这几乎是等效的(它也检测中断)。
     */
    public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }

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

    /**
     * 释放一个许可,并将其返回给信号量。
     *
     * 1.释放一个许可证,可用的许可证数量增加一个。如果有任何线程试图获得许可证,那么将选择一个线程并授予刚刚释放的许可证。出于线程调度的目的,该线程已(重新)启用。
     * 2.没有要求释放许可的线程必须通过调用  acquire 来获得该许可。信号量的正确用法是通过应用程序中的编程约定来确定的。
     */
    public void release() {
        sync.releaseShared(1);
    }

    /**
     * 从这个信号量中获取给定数量的许可,阻塞直到所有许可都可用,或者线程被中断。
     *
     * 1.获取给定数量的许可证(如果它们可用),并立即返回,将可用许可证的数量减少给定的数量。
     * 2.如果可用的许可不足,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到发生以下两种情况之一:
     *    其他线程调用这个信号量的  release() 方法之一,当前线程是下一个被分配许可的线程,可用的许可数量满足这个请求; 或其他线程中断当前线程。
     * 3.如果当前线程:在进入该方法时设置其中断状态; 或在等待许可时被中断。 然后 InterruptedException 被抛出,当前线程的中断状态被清除。
     *
     * 任何分配给该线程的许可都会分配给其他试图获取许可的线程,就像通过调用 release()获得了许可一样。
     */
    public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }

    /**
     * 从这个信号量中获取给定数量的许可,直到所有许可都可用为止。
     *
     * 1.获取给定数量的许可证(如果它们可用),并立即返回,将可用许可证的数量减少给定的数量。
     * 2.如果可用的许可数量不足,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到其他线程调用此信号量的 release 方法之一,当前线程将被分配许可,并且可用许可的数量满足此请求。
     * 3.如果当前线程在等待允许时是被中断,那么它将继续等待,并且它在队列中的位置不受影响。当线程从这个方法返回时,它的中断状态将被设置。
     */
    public void acquireUninterruptibly(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireShared(permits);
    }

    /**
     * 仅当调用时所有许可都可用时,才从此信号量获取给定数量的许可。
     *
     * 1.获取给定数量的许可(如果它们可用),并立即返回值 true,将可用许可的数量减少给定的数量。
     * 2.如果可用的许可数量不足,则该方法将立即返回值  false,可用的许可数量不变。
     * 3.即使这个信号量已经被设置为使用公平排序策略,如果有一个许可可用,调用  tryAcquire 将立即获得一个许可,无论当前是否有其他线程正在等待。
     * 这个“闯入”行为在某些情况下是有用的,即使它破坏了公平。
     * 如果你想遵守公平性设置,那么使用 tryAcquire(int, long, TimeUnit) tryAcquire(permissions, 0, TimeUnit. seconds) ,这几乎是等效的(它也检测中断)。
     */
    public boolean tryAcquire(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.nonfairTryAcquireShared(permits) >= 0;
    }

    /**
     * 从这个信号量中获取给定数量的许可,如果所有许可都在给定的等待时间内可用,并且当前线程没有被线程中断。
     *
     * 1.获取给定数量的许可,如果它们可用并立即返回,值为  true,将可用许可的数量减少给定的数量。
     * 2.如果可用的许可不足,则当前线程将出于线程调度目的而被禁用,并处于休眠状态,直到发生以下三种情况之一:
     *     其他线程调用这个信号量的 release() release 方法之一,当前线程是下一个被分配许可的线程,可用的许可数量满足这个请求; 或一些其他线程中断当前线程; 或等待时间已过。
     * 3.如果获得了许可,则返回值  true。
     * 4.如果当前线程: 在进入此方法时设置其中断状态; 或在等待获取许可时,线程中断。然后 InterruptedException 被抛出,当前线程的中断状态被清除。
     *     任何分配给这个线程的许可,都被分配给了其他试图获得许可的线程,就好像这些许可是通过调用 release() 获得的一样。
     * 5.如果指定的等待时间过去了,则返回值  false 。如果时间小于或等于零,该方法将根本不等待。任何分配给这个线程的许可,都被分配给了其他试图获得许可的线程,就好像这些许可是通过调用  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));
    }

    /**
     * 释放给定数量的许可,并将它们返回给信号量。
     *
     * 1.释放给定数量的许可证,将可用许可证的数量增加该数量。如果有任何线程试图获得许可,那么将选择一个线程并授予刚刚释放的许可。
     * 如果可用许可的数量满足线程的请求,那么该线程将(重新)为线程调度目的启用;否则线程将等待,直到有足够的许可可用。
     * 如果在这个线程的请求被满足之后,仍然有可用的许可证,那么这些许可证将依次分配给试图获得许可证的其他线程。
     * 2.没有要求释放许可证的线程必须通过调用 acquire 来获得许可证。信号量的正确用法是通过应用程序中的编程约定来确定的。
     */
    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();
    }

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

    /**
     * 如果这个信号量的公平性设置为true,则返回 true 。
     */
    public boolean isFair() {
        return sync instanceof FairSync;
    }

    /**
      * 查询是否有线程正在等待获取。请注意,因为取消可能在任何时候发生,返回  true 并不保证任何其他线程将获得。这种方法主要用于监视系统状态。
     */
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

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

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

    public String toString() {
        return super.toString() + "[Permits = " + sync.getPermits() + "]";
    }
}
posted @ 2023-02-18 09:06  Cool_Yang  阅读(40)  评论(0编辑  收藏  举报