Using Semaphore to control a resource pool

The Semaphore in effect manages a set of "permissions". A thread asks to be granted a permission before continuing to access the pool. When it has finished, it releases the permission. If when a thread asks for a permission, the maximum number have already been given out, then the requesting thread blocks until permission becomes available, up to some maximum time specified by the programmer. So our resource pool idiom looks something like this:

public class ResourcePool<T> {
  private final Semaphore sem =
     new Semaphore(MAX_RESOURCES, true);
  private final Queue<T> resources =
     new ConcurrentLinkedQueue<T>();
  
  public T getResource(long maxWaitMillis)
     throws InterruptedException, ResourceCreationException {

    // First, get permission to take or create a resource
    sem.tryAcquire(maxWaitMillis, TimeUnit.MILLISECONDS);

    // Then, actually take one if available...
    T res = resources.poll();
    if (res != null)
      return res;

    // ...or create one if none available
    try {
      return createResource();
    } catch (Exception e) {
      // Don't hog the permit if we failed to create a resource!
      sem.release();
      throw new ResourceCreationException(e);
    }
  }
  public void returnResource(T res) {
    resources.add(res);
    sem.release();
  }
}

The true parameter passed into the constructor declares the semaphore as fair: that is, threads waiting for a resource will be given the next free one on a more or less first-come-first-served basis. For locks in many situations, this isn't what we want, because the code that holds a lock is generally a quick in-and-out operation, so even in the unfair case, waiters won't on average wait too long, and we'll get better throughput. In the case of access to something like a database connection, threads may well hold on to them for several milliseconds (or even several hundred), so a thread could wait an unacceptable amount of time if other threads "jump the queue". Of course, this is something that you must decide, depending on how long threads will be holding on to the particular resources in question.

Note that Semaphore controls purely the number of accesses to the pool and waiting for/interrupting access to the pool; it doesn't control how we manage synchronization when actually taking a resource from the list or putting it back. In this example, we use a ConcurrentLinkedQueue, which allows us to concurrently add to the end of the queue and take from the beginning.

Notice another subtlety in the above example: if during resource allocation we fail to create a resource, then we must immediately release the permit that we acquired. Just because we failed to create a resource shouldn't generally prevent another thread (or even our thread) from trying again later!


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.