如果我们正在同步函数,为什么需要信号量



下面的代码是我从Semaphore上的oracle文档中获取的。

我的问题是,如果我无论如何都在同步getNextAvailableItem()和markAsUnused方法,这将阻止其他99个线程进入主函数,该函数要么给我共享资源,要么接受它。那么信号量的用途是什么,因为不管是99还是1000个线程在等待锁定。

 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();
    }
    // Not a particularly efficient data structure; just for demo
    protected Object[] items = ... whatever kinds of items being managed
    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; 
    }
       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;
    }
   }

问题是:是否希望线程阻塞,直到有可用项?

如果是的话,那么信号量是必要的,因为它基本上是一个计数器,当它已经为零时就会阻塞,并且线程正在试图获取它

很明显,您可以尝试在没有信号量的同步方法中自己实现这一点(即使用一些低级别的构造,如wait/notify),但这只是对难以发现的错误的邀请。

如果不需要阻塞,并且您对方法向调用方返回null很满意,那么您可以跳过信号量。

要问的问题是,如果调用getItem时项不可用,该怎么办。很明显,这里的要求是等待一个可用的。仅使用synchronized是行不通的,因为它不允许在解锁状态下等待(在锁定状态下等待会阻止任何人释放任何东西)。

计数信号量是实现这一点的最有效方法。当您的available.acquire();成功返回(即不抛出InterruptedException)时,已知至少有一个项目可用(因为项目计数与信号量的初始计数匹配,并且可用项目的计数与信号值计数器保持同步)。

有其他选择,但没有那么好。

您可以使用互斥和条件变量,在Java中称为LockCondition(或者您也可以使用Java的waitnotify以及synchronized)。getItem将有一个循环:锁定互斥锁,查看项是否可用:如果可用,则获取它并返回,否则等待条件变量并重试。然后putItem会在发布项目后发出条件变量的信号,这会唤醒任何服务员,这样他们就知道要检查是否有空。但这是一个更多的代码,可能效率更低(尤其是如果所有服务员都被唤醒,而不是只有一个),而且Java Condition没有提供公平排队的方法,而Semaphore具有公平布尔属性,所以服务员是按顺序服务的。

然后,一个更糟糕的选择是只有一个Locksynchronized块,然后在循环中检查项目是否可用,并在可能的情况下接受它,否则(可选)睡眠一段时间,然后重试。但这是低效的,丑陋的,不能扩展到许多线程,并且会让任何有经验的开发人员感到尴尬。

最新更新